IOS开发之3DTouch功能的开发详解

3DTouch是在6s之后苹果的一项技术,通过屏幕的压力来弹出快捷窗,从而更便捷的操作。以前我不怎么用,但是自从发现支付宝直接弹窗支付扫码之后,发现这个功能也是很不错的,下面开始开发,注意,这个功能只能在6s和6s之上的手机上面真机运行,Xcode的模拟器是不支持的。

3DTouch的改进包括三个方面

  • Quick Actions(点击icon的快捷方式)

  • Peek&Pop(应用内快速预览内容)

  • UITouch(增加了压力和最大压力)

一、Quick Actions(点击icon的快捷方式)

Quick Actions就是不同力度点击桌面icon时弹出的快捷方式

IMG_2929.jpg

这个方式主要就是在软件后台运行时或者没有打开时用来直接跳转到相应功能的方式,可以通过Info.plist创建,也可以通过代码方式创建。

mark:搜了几个资料都说这个弹窗里面最多有四个选项,我创建了五个的确只能显示四个,但是QQ里面就是五个,暂不清楚怎么弄超过四个的(2016.11.23续,终于知道了,自己加的上限的确是四个,当在appstore审核通过之后,苹果会默认给你加一个分享的,所以会显示五个)。

1.1、通过info.plist文件创建快捷菜单

在info.plist文件里面添加对应的key即可

屏幕快照 2016-10-28 上午10.29.20.jpg

在Info.plist文件中添加一个UIApplicationShortcutItems的数组,在该数组中填写下面对应的key即可

必填项(下面两个键值是必须设置的):

UIApplicationShortcutItemType //这个键值类似于一个tag值 
UIApplicationShortcutItemTitle //这个键值设置标签的标题

选填项(下面这些键值不是必须设置的):

UIApplicationShortcutItemSubtitle     //设置标签的副标题
//Icon类型和Icon文件任选其一即可
UIApplicationShortcutItemIconType     //设置标签Icon类型
UIApplicationShortcutItemIconFile     //设置标签的Icon文件
UIApplicationShortcutItemUserInfo     //设置信息字典(用于传值)

其中Icon类型是用苹果默认的图标,Icon文件则是用用户自己设置的图片,两个任选其一即可,用户自定义图片会默认成单色黑白的,比如第一张图的那个h5的标志,里面的5是透明的,不然会成为一整张黑色图。

Icon类型在代码里面创建的时候可以看到,这里列举一下苹果默认的几个类型

    UIApplicationShortcutIconTypeCompose,
    UIApplicationShortcutIconTypePlay,
    UIApplicationShortcutIconTypePause,
    UIApplicationShortcutIconTypeAdd,
    UIApplicationShortcutIconTypeLocation,
    UIApplicationShortcutIconTypeSearch,
    UIApplicationShortcutIconTypeShare,
    UIApplicationShortcutIconTypeProhibit       NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeContact        NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeHome           NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeMarkLocation   NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeFavorite       NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeLove           NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeCloud          NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeInvitation     NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeConfirmation   NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeMail           NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeMessage        NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeDate           NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeTime           NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeCapturePhoto   NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeCaptureVideo   NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeTask           NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeTaskCompleted  NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeAlarm          NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeBookmark       NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeShuffle        NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeAudio          NS_ENUM_AVAILABLE_IOS(9_1),
    UIApplicationShortcutIconTypeUpdate         NS_ENUM_AVAILABLE_IOS(9_1)

info.plist文件设置之后运行就会有快捷菜单了

1.2、通过代码创建快捷菜单

UIApplicationShortcutItem这个类就是用来创建快捷菜单的,只要软件打开后执行了创建代码,就会动态创建快捷菜单,创建过程就如下面的代码,其实只是动态的填写值了

-(void)createTouch
{
    //默认样式
    UIApplicationShortcutIcon *icon1 = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeLove];
    //自定义图片
    UIApplicationShortcutIcon *icon2 = [UIApplicationShortcutIcon iconWithTemplateImageName:@"sa.png"];
    
    NSDictionary *userdic = [NSDictionary dictionaryWithObjectsAndKeys:@"damon",@"name",@"http://www.hudongdong.com",@"blogurl", nil];
    
    UIApplicationShortcutItem *item1 = [[UIApplicationShortcutItem alloc] initWithType:@"damonText1" localizedTitle:@"动态创建第一个标题" localizedSubtitle:@"这个是动态创建的" icon:icon1 userInfo:nil];
     UIApplicationShortcutItem *item2 = [[UIApplicationShortcutItem alloc] initWithType:@"damonText2" localizedTitle:@"动态创建第二个标题" localizedSubtitle:@"这个也是动态创建的,有用户信息" icon:icon2 userInfo:userdic];
    [UIApplication sharedApplication].shortcutItems=[NSArray arrayWithObjects:item1,item2, nil];
    
    //再重复动态创建会覆盖掉前面动态创建的,但是不会覆盖掉info里面创建的
//     UIApplicationShortcutItem *item3 = [[UIApplicationShortcutItem alloc] initWithType:@"damonText2" localizedTitle:@"第三个标题" localizedSubtitle:@"这个也是动态创建的" icon:icon2 userInfo:nil];
//    [UIApplication sharedApplication].shortcutItems = [NSArray arrayWithObjects:item3, nil];
}

两个创建方式是可以混合的创建的,在info.plist创建的快捷菜单选项会比动态创建的靠前,这个就是创建过程。

1.3、点击快捷菜单响应函数

创建完快捷菜单之后,点击菜单需要响应相应的函数才有意义,所以需要在AppDelegate里面添加代理函数,

// except when -application:willFinishLaunchingWithOptions: or -application:didFinishLaunchingWithOptions returns NO.
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void(^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) __TVOS_PROHIBITED

这个函数就是当点击相应菜单的时候,打开软件会响应的函数。

苹果已经说明了,如果在-application:willFinishLaunchingWithOptions: 或者-application:didFinishLaunchingWithOptions这个加载完成的函数中返回了NO,就不会执行,是为了区分下面这种情况。

在软件后台运行的情况下,点击菜单,软件每次都会响应performActionForShortcutItem这个函数,但如果软件没有后台运行的话,就需要区分用户是点击icon打开了软件还是通过快捷菜单打开了软件,两者都会调用application:didFinishLaunchingWithOptions这个函数,但是如果这个函数返回的NO,那启动之后,调用完application:didFinishLaunchingWithOptions这个函数之后,是不会再去调用performActionForShortcutItem这个函数,后面在后台运行之后,再去点击快捷菜单才会调用performActionForShortcutItem这个函数,如果返回的是YES,那就打开之后,回去调用performActionForShortcutItem这个函数。

当然在application:didFinishLaunchingWithOptions这个函数中,可以通过Options中的UIApplicationLaunchOptionsShortcutItemKey这个key值来判断是否是通过快捷菜单启动的软件,比如下面这样

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    //判断是通过桌面打开软件还是通过快捷按钮打开
    //快捷按钮打开软件 显示快捷按钮的type
    if ([launchOptions objectForKey:UIApplicationLaunchOptionsShortcutItemKey]) {
        UIApplicationShortcutItem*item = [launchOptions objectForKey:UIApplicationLaunchOptionsShortcutItemKey];

        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 10, 300, 300)];
        [label setText:[NSString stringWithFormat:@"%@",item.type]];
        [label setTextColor:[UIColor redColor]];
        [self.window.rootViewController.view addSubview:label];
        //可以传值
        if (item.userInfo) {
            [label setText:[NSString stringWithFormat:@"%@,%@",[item.userInfo objectForKey:@"name"],[item.userInfo objectForKey:@"blogurl"]]];
        }
        //return NO的话开启不再执行响应快捷按钮的函数
        //return YES的话执行响应快捷按钮的函数
        return NO;
    }
    return YES;
}

//响应快捷按钮的函数
// except when -application:willFinishLaunchingWithOptions: or -application:didFinishLaunchingWithOptions returns NO.
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void(^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) __TVOS_PROHIBITED
{
    NSLog(@"%@",shortcutItem.type);
    
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(200, 10, 300, 300)];
    [label setText:[NSString stringWithFormat:@"%@",shortcutItem.localizedTitle]];
    [label setTextColor:[UIColor redColor]];
    [self.window.rootViewController.view addSubview:label];
}

这个是在点击快捷菜单的时候,显示快捷菜单的type值,在启动软件的时候,如果返回YES,那么就会两个label创建都执行

IMG_2928.PNG

(返回YES)

如果返回了NO,那就是只执行一次didFinishLaunchingWithOptions里面的函数

IMG_2927.PNG

(返回NO)

这样就可以通过不同的key值来响应函数了,并且那个userInfo是可以传值的,比如上面这个userinfo在代码创建的时候创建的,然后打开之后会传进来

IMG_2945.PNG

二、Peek&Pop

Peek&Pop是在应用中,点击相应的视图来预览图片,比如safari浏览器里面保存图片的时候用力按的时候的效果,会分为预览和重按两个层次,当用力时就可以预览,但是用力再大点就成了重按的效果。

这个作用就是比如点击上面图中的感叹号的时候,预览了一个页面,当重按的时候,就会跳转到这个页面。

IMG_2938.jpg

2.1、页面预览(peek)

这个功能其实是两个ViewController的之间的预览,比如在ViewController中预览SecondViewController的内容,可以这么做。

首先把ViewController中继承这个代理UIViewControllerPreviewingDelegate,然后将ViewController和对应的点击视图注册为响应

-(void)testPeek{
    
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 200, 100, 100)];
    [imageView setImage:[UIImage imageNamed:@"sa.png"]];
    [self.view addSubview:imageView];
    [imageView setUserInteractionEnabled:true];
    
    [self registerForPreviewingWithDelegate:self sourceView:imageView];
}

这样就可以点击图片去响应力度了,然后在ViewController中实现代理函数

//预览
- (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location NS_AVAILABLE_IOS(9_0)
{
    SecondViewController *v = [[SecondViewController alloc] init];
    return v;
}

//重按进入
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit NS_AVAILABLE_IOS(9_0)
{
    NSLog(@"重按进入");
    [previewingContext setSourceRect:CGRectMake(10, 10, 100, 200)];
    SecondViewController *v = [[SecondViewController alloc] init];
    [self presentViewController:v animated:YES completion:nil];
}

这样就可以用力按压该图片的时候预览SecondViewController里面的内容,继续用力,就会跳转进入SecondViewController

2.2、POP往上弹出菜单

这个就是在用力预览时,将这个预览界面往上拖动的时候可以弹出一个菜单。

IMG_2940.jpg

实现方法就是在预览页面,也就是在SecondViewController中,重新定义previewActionItems,在SecondViewController中重写这个函数

- (NSArray <id <UIPreviewActionItem>> *)previewActionItems

比如这样

//快捷键弹出窗
- (NSArray <id <UIPreviewActionItem>> *)previewActionItems
{
    NSLog(@"previewActionItems");
    // 普通样式
    UIPreviewAction *action1 = [UIPreviewAction actionWithTitle:@"我" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
        NSLog(@"Aciton1我");
    }];
    
    //已被选择的样式 后面有个对勾
    UIPreviewAction *action2 = [UIPreviewAction actionWithTitle:@"爱" style:UIPreviewActionStyleSelected handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
        NSLog(@"Aciton2爱");
    }];
    
    //警示样式(红色字体)
    UIPreviewAction *action3 = [UIPreviewAction actionWithTitle:@"你" style:UIPreviewActionStyleDestructive handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
        NSLog(@"Aciton3你");
    }];
    
    //已被选择的样式 后面有个对勾
    UIPreviewAction *action4 = [UIPreviewAction actionWithTitle:@"跳转到第三个页面" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
        ThirdViewController *vc =[[ThirdViewController alloc] init];
        [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:vc animated:true completion:nil];
    }];
    
    NSArray *actions = [NSArray arrayWithObjects:action1,action2,action3,action4, nil];
    
    return actions;
    
}

这样就可以了,就会在往上拖动页面的时候,弹出这几个选项,然后在代码块里面实现对应功能即可。

三、UITouch

在UITouch中,新添加了力度force和maximumPossibleForce这两个属性,就是检测按压的力度,UIViewController继承了UIResponder,所以可以检测按压的力度,在ThirdViewController示例中,当按压时,动态显示按压力度

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
    [self.myLabel setText:[NSString stringWithFormat:@"%f",touches.anyObject.force]];
    if (touches.anyObject.force< 1.0f) {
        [self.myLabel setText:@"快用力"];
    }
    if (touches.anyObject.force> 5.0f) {
        [self.myLabel setText:@"你太用力了"];
    }
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
    [self.myLabel setText:[NSString stringWithFormat:@"最大承受压力:%f",touches.anyObject.maximumPossibleForce]];
    NSLog(@"%s",__FUNCTION__);
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
    NSLog(@"%s",__FUNCTION__);
}
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1)
{
    NSLog(@"%s",__FUNCTION__);
}

这样在Label中就可以显示按压的力度,就可以通过不同力度实现不同功能

IMG_2942.jpg

最大力度和力度都是只能读的,可以根据力度来实现逻辑即可

IMG_2943.jpg

四、Demo下载:

GitHub下载:https://github.com/DamonHu/Iphone3DtouchDemo

GitOsc下载:http://git.oschina.net/DamonHoo/Iphone3DtouchDemo

五、参考文档

Last modification:January 1st, 1970 at 08:00 am
如果看了这个文章可以让你少加会班,可以请我喝杯可乐
已打赏名单
微信公众号

9 comments

  1. 进击的小年轻

    大神 你好, 我想问一下 为什么我的那个默认的分享就是显示不出来呢? 需要联系苹果吗

    1. 进击的小年轻
      @进击的小年轻

      OK, 看了 是添加了 Today Extension 用到这个widget

    2. Damon胡东东
      @进击的小年轻

      他那个应该是苹果默认把widget加在上面了,改天看看软件的widget怎么做

    3. 进击的小年轻
      @进击的小年轻

      就是在下面有一个单独的框 显示别的内容

    4. 进击的小年轻
      @进击的小年轻

      大神, 再问个问题, 类似京东,网易新闻.饿了么这三个APP 的3DTouch 效果怎么做的 你有思路吗

    5. 进击的小年轻
      @进击的小年轻

      谢谢 大神 了解! 嘿嘿

    6. Damon胡东东
      @进击的小年轻

      审核通过之后,从appstore下载的版本才会有,自己用xcode安装的是没有的

  2. 事在人违

    同桌厉害了

    1. Damon胡东东
      @事在人违

      [挤眼]

Leave a Comment