3D Touch是我一直想学习的功能,晚上无意间在手机上按压了不同的应用(这么一说暴露了自己用的iPhone 6以上的设备),发现常用的应用都加了该功能。为了自己的好奇心,跟着官方文档做了一遍,把过程中的思路和易错点记录下来。
目前3D Touch功能只能在iPhone6并且支持3D Touch的设备中使用
1 主屏幕Icon的3D Touch效果
对于能开启3D Touch 的设备而言,在主屏幕中以一定的力度按压应用的图标,会弹出一个预览的功能框,功能框中包括一些功能的快捷按钮,点击之后会启动应用并快捷的开启应用相关功能。具体的实现的步骤如下:
步骤一 UIApplicationShortcutItem 添加快捷按钮
每一个快捷按钮都是一个UIApplicationShortcutItem,可以通过两种方法设置:
① 在应用的Info.plist文件中设置静态快捷按钮
这种方法很简单,在UIApplicationShortcutItems键中添加快捷键的信息。UIApplicationShortcutItem属性包括:
UIApplicationShortcutItemType(必选):快捷键的唯一标识符,用来识别以便于实现相对应的功能
UIApplicationShortcutItemTitle(必选):标题,黑色字体
UIApplicationShortcutItemIconType(可选):显示的图标,图标可以是系统或者自定义
UIApplicationShortcutItemUserInfo(可选):用户自定义信息
UIApplicationShortcutItemSubtitle(可选):副标题,灰色字体
② 在应用中设置动态快捷按钮
UIApplicationShortcutIcon *icon = [UIApplicationShortcutIcon iconWithTemplateImageName:@"pay-select.png"];
UIApplicationShortcutItem *home = [[UIApplicationShortcutItem alloc] initWithType:@"lizhou.home" localizedTitle:@"热门单品" localizedSubtitle:@"热卖的物品任你挑" icon:icon userInfo:nil];
[UIApplication sharedApplication].shortcutItems = @[home];
重点
1.设置Item的title和subtitle
title和subtitle分别只占用一行,多余的文本系统会默认添加省略号。
2.设置icon
快捷键的图标可选择自定义图标或系统提供的图标。自定义的图片大小应为35x35,而且因为系统会统一模糊化图片,所以提供的图标尽量为单色,不然只有可能只显示黑色的正方形而不使用提供的图标。
@interface UIApplicationShortcutIcon : NSObject <NSCopying>
// 使用系统提供的图片
+ (instancetype)iconWithType:(UIApplicationShortcutIconType)type;
// 创建自定义的图片,将会使图片模糊成系统定义icon的风格
+ (instancetype)iconWithTemplateImageName:(NSString *)templateImageName;
@end
自定义图标在Info.plist中可以直接填图片的名称。
3. UIApplicationShortcutItemUserInfo 用户自定义信息
之前一直不能理解静态和动态设置快捷键之间有什么差别,看了几个应用如微信、微博、支付宝等都是普通的快捷键,直到看到快看漫画,该应用的"继续阅读"项中添加了动态的效果,才意识到两者之间最大的差异性就是UIApplicationShortcutItemUserInfo这个属性。
4. 快捷键的数量
问题:在官方文档中明确的标记了应用最多只能设置4个自定义的item,为什么快看会有5个按钮?
回答:快看漫画中自定义的item就是四个,最后一个 "分享 ‘快看漫画’"项是苹果为每一个提交到应用商店的应用默认添加的,所以准确的说:
开发者自定义的item最多为4个,用户可视化的item最多为5个
步骤二 识别到用户对快捷键的点击
应用识别是由3D Touch的点击进行应用并执行相关的行为的方法分为两种情况:
① 应用处于运行中的状态
//app仍然在运行的时候,点击菜单项会触发以下的方法:
-(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
if([shortcutItem.type hasSuffix:@"search"]){
// 搜索更多
} else if ([shortcutItem.type hasSuffix:@"share"]) {
//分享
NSString *title =shortcutItem.localizedTitle;
NSDictionary *userInfo = shortcutItem.userInfo;
UINavigationController *nav = (UINavigationController *)[_tabBarController selectedViewController];
[nav pushViewController:[[TestViewController alloc] init] animated:YES];
}
completionHandler(YES);
}
② 应用被kill后,没有运行的情况下
//在app 被kill的时候,点击菜单项会触发以下的方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_isEnterFrom3DTouch = true;
//如果是通过3D Touch点击shortItem进入应用的话,那么UIApplicationLaunchOptionsShortcutItemKey一定返回相应的UIApplicationShortcutItem。
UIApplicationShortcutItem *shortItem =[launchOptions objectForKey:UIApplicationLaunchOptionsShortcutItemKey];
if (shortItem) {
//如果从3D Touch点击item进行,那么返回false,不再触发-(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler方法
_isEnterFrom3DTouch = false;
}
return _isEnterFrom3DTouch;
}
其实在设置item和收到item 的用户响应都不需要判断设备是否支持3D Touch,如果支持,按压才能识别。识别之后系统才会执行应用设置的3D Touch功能。
对于Icon设置的3D Touch以及相关的响应接收方法处理差不多了,下面是对应用内的3D Touch配置。
2 应用内的3D Touch效果
上图中能清楚的看到整个过程包括两个阶段:
第一阶段 :模糊化周围,弹出一个类似弹框的界面 ----peek
第二阶段:加重按压,进入一个视图控制器 --- pop
实现整个效果的步骤如下:
步骤一 明确哪个控件有3D Touch功能并注册
像微博,一个cell中有两个3D Touch控件,而且两个控件的预览图也不同,并且点击在cell别的地方时不会显示3D Touch的效果。
所以需要对一个cell内部局部view进行3D Touch的注册,而不是像官方例子中对整个tableView进行注册:
if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
[self registerForPreviewingWithDelegate:self sourceView:self.tableView];
}
对于整体而言,首先我们注册的是具有3D Touch功能的控件 ---cell中的imageView(以微博为例),所以cell需要实现UIViewControllerPreviewingDelegate协议
@interface LZImageCell : UITableViewCell<UIViewControllerPreviewingDelegate>
并且实现UIViewControllerPreviewingDelegate协议的两个方法:
//显示 pop 的视图控制器
-(void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit
{
if ([self.previewDelegate respondsToSelector:@selector(previewingContext:commit:)]) {
[self.previewDelegate previewingContext:previewingContext commit:viewControllerToCommit];
}
}
//显示peek 的视图控制器
-(UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location
{
if ([self.previewDelegate respondsToSelector:@selector(previewingContext:viewControllerForLocation:)]) {
return [self.previewDelegate previewingContext:previewingContext location:location];
}
return nil;
}
因为cell本身是无法处理视图控制器之间的push,并且注册只能在ViewController类中注册,所以设置一个delegate,将数据传递到相应的viewController中进行处理:
-(UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext location:(CGPoint)location
{
//根据点击位获得当前点击cell的indexPath值
NSIndexPath *indexpath = [self.tableView indexPathForRowAtPoint:location];
//得到当前点击的cell的位置
CGRect cellFrame = [self.tableView cellForRowAtIndexPath:indexpath].frame;
//找到cell中响应3D Touch的图片控件位置和大小
cellFrame.size.height = 200;
cellFrame.origin.x = 10;
cellFrame.size.width = [UIScreen mainScreen].bounds.size.width - 20;
TestViewController *test = [[TestViewController alloc] init];
//这个值是设置显示test控制器中view的大小
test.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width - 20, 200);
//周围都是模糊化,只有cellFrame值设置的部分为清晰的状态
previewingContext.sourceRect = cellFrame;
return test;
}
//pop 显示视图控制器
//commitViewController是在peek方法中设置的ViewController(如: TestViewController)
-(void )previewingContext:(id<UIViewControllerPreviewing>)previewingContext commit:(UIViewController *)commitViewController
{
[self.navigationController pushViewController:commitViewController animated:YES];
}
将peek和pop设置完成之后,只差最后一步了 ---- 注册cell中的imageView
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
LZImageCell *cell = [tableView dequeueReusableCellWithIdentifier:imageCellStr forIndexPath:indexPath];
cell.previewDelegate = self;
if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
//注册
[self registerForPreviewingWithDelegate:cell sourceView:cell.backImageView];
} else {
NSLog(@"3D Touch 不可用");
}
return cell;
}
注意:如果你还按压imageView没有任何反应,说明你没有将图片的交互功能打开
backImageView.userInteractionEnabled = YES;
按压也是touch的一种,需要响应的,是不是傻!!
步骤二 对预览页中添加按钮
用户在peek页中向上滑动会出现设置的按钮,所以按钮设置在peek方法返回的ViewController中(如:TestViewController)
-(NSArray<id<UIPreviewActionItem>> *)previewActionItems
{
UIPreviewAction *likeAction = [UIPreviewAction actionWithTitle:@"喜欢" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
}];
UIPreviewAction *cancleAction = [UIPreviewAction actionWithTitle:@"保存" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
}];
官方文档自己都说设置的UIPreviewAction按钮和UIAlertAction很类似,所以对于style的设置没什么不同。
如果按钮数过多,可以将类似的按钮放在同一个按钮组中
点击之后才会显示相关的按钮。
就我个人认为实现3D Touch的实现并不难,但是有一些细节和思路还是要好好优化,等将官方的相关文档全部看了一遍之后,再好好总结一次。