跟着官方文档学习3D Touch

前言

关于3D touch苹果官方文档是这么开始介绍的:


3D Touch官网截图.png

大意如下:
iOS9开始,所有新的手机都增加了一个三维的用户接口界面。

  • 在app外,用户按压主屏幕图标就可以立即访问应用程序所提供的功能。
  • 在app内,用户按压视图就可以预览额外的新内容,体验快速访问的特性。
    备注:app外,是指app未启动或者处于后台状态;app内,是指app已经启动且处于前台激活状态。

如果上面的简介没有看懂,可以用官方文档中下面这段话来进一步解释。

3D Touch官方截图.png

大意如下:
3DTouch为iOS9用户提供了一个额外维度的人机交互界面。在支持3DTouch的设备上,在app外,人们可以在主屏幕上按压app图标来快速选择app可执行的某个具体的操作。在app内,人们可以使用不同的压力来得到不同的内容查看效果:1.预览视图 2.打开一个单独的视图控制器界面查看视图,进而进行其他交互。
苹果的3D Touch分为两类,一类是app外,在主屏幕上按压app的图标,可以在app图标旁边弹出一个带有快捷操作项的菜单。另一类是在app内,稍用力按压某个视图,可以预览除去该视图额外的内容,再稍加用力按压屏幕,可以弹出另一个控制器界面,这个控制器界面就是点击这个被按压的视图将会跳转的控制器。
下面我就以app内app外两个维度来跟着官方文档解释3D Touch。


(一)app外3D Touch—Home Screen Quick Actions

所谓app外3D Touch也就是苹果官方所说的Home Screen Quick Actions.

Home Screen Quick Actions简介.png

大意如下:
过去,拥有iPhone的用户,总是可以通过轻轻的点击(tap)app图标的方式来启动一个app。或者通过按住屏幕上某个app图标不松手的方式使app处于抖动可编辑状态,进而来对主屏幕进行编辑,此处的编辑是指:删除app或者移动app等操作。
现在,拥有iPhone6s或者iPhone6s Plus的用户,除了可以进行以上的两个操作外,用户还可以通过按压iPhone6s或者iPhone6s Plus应用图标的方式,获得由一组快速操作按钮组成的菜单,也就是上图中的菜单。菜单中的每个可以触发操作的按钮,苹果称之为quick actions,我在此称之为快捷操作项。当用户选择了一个快捷操作项时,app将启动或被激活,然后app delegate对象将会接收到与当前触发的这个快速启动项相关的消息。


如上图所示,这是我在官方文档中截取的图片,上图中矩形菜单中的四个item就是我们所说的“quick action”译为:快速操作项。而这4个quick action共同组成了“a set of quick actions”,也就是上图屏幕截图中的"小菜单"。用户能够通过这组快速操作项来对app进行快速访问,看起来有点快捷键的意思(这只是笔者本人的观点,也是我译为快捷操作项的原因)。

The best quick actions anticipate and accelerate a user’s interaction with your app.译:这是最好的快捷操作方式,这种方式可以让用户提前预见app并且加速了用户和程序间的交互。 苹果官方是这么定义快捷操作项的。

以上是根据苹果官方文档进行的翻译,通篇下来,能看出苹果是一直在夸这个quick actions。功能虽好,但再好的功能也是需要我们程序员来一步一步集成的。接下来主要介绍集成这个quick actions功能。

Home Screen Quick Actions集成

Home Screen Quick Actions分为两种:static quick actions和dynamic quick actions。iOS9 SDK提供了一组相关的API,能够让我们快速集成static(静态的)或者dynamic(动态的)quick actions。
那么什么是static quick actions,什么又是dynamic quick actions?

集成静态和动态的快捷操作项.png
  • 定义静态快捷操作需要在app的Info.plist文件中配置UIApplicationShortcutItems这个Key,UIApplicationShortcutItems这个Key对应的是一个数组。到此为止,我猜测,这个数组内应该放置的是一个字典, 每个字典又对应着一个quick action。这个猜测后面进行验证。
  • 定义一个动态快捷操作需要用到“UIApplicationShortcutItem”类和相关的API创建UIApplicationShortcutItem对象(所谓API也就是苹果官方SDK提供的一些可供我们调用或重写的方法)。然后需要用UIApplication的sharedApplication方法获取UIApplication对象,然后用UIApplication对象的“shortcutItems”属性,当然shortcutItems是UIApplication的一个新属性。

注意:两种定义快捷操作项的方式都能显示两行文本和一个可选的图标。
注意:quick actions最多显示4项。也就是说,无论是静态还是动态,这两种定义快捷操作项的方式最多显示四个快捷操作项。

static quick actions集成

无论是集成动态的还是静态的Home Screen Quick Actions都需要先来了解下UIApplicationShortcutItems。


UIApplicationShortcutItems.png

Info.plist中的UIApplicationShortcutItems指app的“static quick actions”,也就是指那些静态快捷操作项。UIApplicationShortcutItems这个key对应着一个字典数组,每一个字典代表一个快捷操作项。这个字典描述了这个快捷操作项的详细信息。这个字典内可以包括快捷操作项的title(标题)、type(类型)、icon(图标)、userInfo(用户信息)等。

我们可以用这个数组字典为我们的app指定静态快捷操作项。当用户在支持3D Touch的设备上按压主屏幕上的app图标时候,显示在主屏幕上的快捷操作项的个数是由系统决定的。

我们在Info.plist文件中定义的静态快捷操作项的顺序(数组中字典的先后顺序)也就是静态快捷操作项在屏幕上显示的顺序(默认第一个显示在最下面,也就是这些快捷操作项在屏幕上自下向上排列)。
系统会优先加载Info.plist中配置的快捷操作项,只有在Info.plist中定义的静态快捷操作项不够4个的时候,才会去加载动态快捷操作项。

上面已经说过“UIApplicationShortcutItems这个key对应着一个字典数组,每一个字典代表一个快捷操作项。”但是,这个字典数组最多只能容纳4个字典,也就是说,当我们按压主屏幕上的app图标时,最多会看到4个快捷操作项。确切的说,是系统默认最多显示4个,而不是字典的容量为4,你可以给这个字典数组赋值5个字典,但是最后只会显示前4个字典配置的快捷操作项。


如下屏幕截图显示了如何在Info.plist文件中定义两个静态快捷操作项:


官方文档提供的截屏.png

如下代码是上面Info.plist中两个静态快捷操作项的源码:

<key>UIApplicationShortcutItems</key>
    <array>
        <dict>
            <key>UIApplicationShortcutItemIconFile</key>
            <string>open-favorites</string>
            <key>UIApplicationShortcutItemTitle</key>
            <string>Favorites</string>
            <key>UIApplicationShortcutItemType</key>
            <string>com.mycompany.myapp.openfavorites</string>
            <key>UIApplicationShortcutItemUserInfo</key>
            <dict>
                <key>key1</key>
                <string>value1</string>
            </dict>
        </dict>
        <dict>
            <key>UIApplicationShortcutItemIconType</key>
            <string>UIApplicationShortcutIconTypeCompose</string>
            <key>UIApplicationShortcutItemTitle</key>
            <string>New Message</string>
            <key>UIApplicationShortcutItemType</key>
            <string>com.mycompany.myapp.newmessage</string>
            <key>UIApplicationShortcutItemUserInfo</key>
            <dict>
                <key>key2</key>
                <string>value2</string>
            </dict>
        </dict>
    </array>
key Description
UIApplicationShortcutItemType (required) A required string delivered to your app when the user invokes the corresponding quick action. Your app can use this string to classify quick actions into types, and then to disambiguate among action types it receives. You don’t need to register your quick action types.
UIApplicationShortcutItemTitle (required) A string displayed to the user on the Home screen as the name of the quick action.If the title fits on one line, the system displays it as a single line quick action item. If the title is too long for one line and you have not specified a UIApplicationShortcutItemSubtitle string, the system displays the title on two lines.You can, optionally, internationalize a quick action title by employing your app’s InfoPlist.strings file. For guidance on how to do this, read Localizing Property List Values.
UIApplicationShortcutItemSubtitle An optional string that is displayed to the user on the Home screen, immediately below the corresponding title string.If you specify a subtitle for a quick action, the system displays the quick action title on a single line (perhaps just a portion of the title, followed by ellipsis character), no matter how long the title is.You can, optionally, internationalize a quick action subtitle by employing your app’s InfoPlist.strings file. For guidance on how to do this, read Localizing Property List Values.
UIApplicationShortcutItemIconType An optional string specifying the type of an icon from the system-provided library; see the UIApplicationShortcutIconType enumeration in UIApplicationShortcutIcon Class Reference. The icon is displayed in the set of quick actions for your app, along with the quick action's title, on the Home screen.
UIApplicationShortcutItemIconFile An optional string specifying an icon image to use from the app’s bundle, or the name of an image in an asset catalog. The icon is displayed before quick action title on the Home screen.Icons should be square, single color, and 35x35 points, as shown in these template files and as described in iOS Human Interface Guidelines.If you specify this key, the system ignores the UIApplicationShortcutItemIconType key.
UIApplicationShortcutItemUserInfo An optional, app-defined dictionary. One use for this dictionary is to provide app version information, as described in the “App Launch and App Update Considerations for Quick Actions” section of the overview in UIApplicationShortcutItem Class Reference.
key 描述
UIApplicationShortcutItemType (required) 这是一个必须设置的参数,而不是可选的,且这个参数是一个字符串(NSString)类型。当用户触发相应的快捷操作项时,这个参数会被传递给应用程序,应用程序可以根据这个字符串(type)来对快捷操作项进行分类,然后在接收的类型中进行区分确定被处触发的快捷操作项的类型。而我们不需要注册快捷操作项的类型。简单的说,这个type字符串标记了快捷操作项的类型,当触发快捷操作项时,系统可以根据其类型来进行判断用户当前触发了哪一个快捷操作项,进而进行相应界面的跳转。
UIApplicationShortcutItemTitle (required) 这是一个必须设置的参数,而不是可选的,且这个参数是一个字符串(NSString)类型会被显示在屏幕上。这是快捷操作项的标题。如果标题能够自适应一行,那么系统就以一行的方式显示这个快速启动项。如果这个标题太长导致一行根本容不下且我们没有指定UIApplicationShortcutItemSubtitle(快速启动项的子标题),那么系统就以两行的方式展示这个快速启动项的标题。当然,我们也可以根据实际情况使用app InfoPlist.string文件国际化快速启动项的标题。如果想知道怎么操作,请参考阅读 Localizing Property List Values。
UIApplicationShortcutItemSubtitle 这是一个可选设置的参数,而不是必选的,且这个参数是一个字符串(NSString)类型。这是快捷操作项的子标题,一旦设置会被显示在屏幕上(前提是要按压app图标),它会展示在对应的标题下方。如果我们为一个快捷操作项指定了子标题,那么系统就会以一行的形式显示子标题(也许仅仅是标题的一部分,后面跟着省略号)无论标题有多长。当然,我们也可以根据实际情况使用app InfoPlist.string文件国际化快速启动项的标题。如果想知道怎么操作,请参考阅读 Localizing Property List Values。
UIApplicationShortcutItemIconType 这是一个可选设置的参数,而不是必选的,且这个参数是一个字符串(NSString)类型。他提供了一些默认的图标类型供我们使用。这是一个枚举值,可以在UIApplicationShortcutIcon类中查看 UIApplicationShortcutIconType的枚举值。 设置的图标会被现实在标题后面。
UIApplicationShortcutItemIconFile 这是一个可选设置的参数,而不是必选的,且这个参数是一个字符串(NSString)类型。他代表app’s bundle中某个图标的路径或者代表image asset catalog中某个图片的名字。这个图标会在标题显示之前预先显示出来。图标应该是正方形,35 * 35点的(注意:iOS开发中讲究的是点,而不是像素,在非retina屏幕上,1点 == 1像素,但是在retina屏幕上就不一定)。如果设置了这个key,那么系统就会忽略UIApplicationShortcutItemIconType。
UIApplicationShortcutItemUserInfo 这是一个可选设置的参数,而不是必选的,且这个参数是一个字典(NSDictionary)类型。这个字典可以包含应用程序的版本信息、程序启动或者更新注意事项。具体的可以参考UIApplicationShortcutItem Class Reference

注意:这些key只支持iOS9系统以及更高版本系统。

下面是我自己利用Info.plist文件集成的static quick actions。
如下图,是Info.plist中的配置:

Info.plist中添加名为UIApplicationShortCutItems的Key.png
// delegate.m文件中实现以下方法
// 作用:点击3Dtouch菜单上的某个item跳转到指定界面
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
    if ([shortcutItem.type isEqualToString:@"0"]) {
        NSLog(@"点击了哈哈");
    } else if ([shortcutItem.type isEqualToString:@"1"]) {
        NSLog(@"点击了呵呵");
    } else if ([shortcutItem.type isEqualToString:@"2"]) {
        NSLog(@"点击了呦呦");
    } else {
        NSLog(@"点击了么么");
        
    }
}

如下是运行效果图:
注意:3D Touch的调试只能在真机上,不能够在模拟器上。

Home screen quick action截图.png

dynamic quick actions的集成

与集成静态的快捷操作项不同的是,集成动态的快捷操作项不需要配置info.plist文件。取而代之的是代码配置,接下来,我就要集成下图所示的动态快捷操作项。


动态快捷操作项屏幕截图.png

步骤:

  • 在程序启动完成回调的didFinishLaunchingWithOptions:方法中实现以下代码
  • 在performActionForShortcutItem:方法中针对于点击不同的快捷操作项而进行不同的操作。
  • 完毕
// 以下是全部代码,且都是在delegate.m文件中实现的
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  
    //使用系统提供的ShortcutIcon类型
    UIApplicationShortcutIcon *addOpportunityIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeAdd];
    UIApplicationShortcutItem *addOpportunityItem = [[UIApplicationShortcutItem alloc] initWithType:@"addOpportunity" localizedTitle:@"添加机会" localizedSubtitle:nil icon:addOpportunityIcon userInfo:nil];
    
    UIApplicationShortcutIcon *bookMarkIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeCompose];
    UIApplicationShortcutItem *bookMarkItem = [[UIApplicationShortcutItem alloc] initWithType:@"bookMark" localizedTitle:@"添加小记" localizedSubtitle:nil icon:bookMarkIcon userInfo:nil];
    
    UIApplicationShortcutIcon *searchGuestIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeSearch];
    UIApplicationShortcutItem *searchGuestItem = [[UIApplicationShortcutItem alloc] initWithType:@"searchGuest" localizedTitle:@"搜索客户" localizedSubtitle:@"通过关键字搜索感兴趣客户" icon:searchGuestIcon userInfo:nil];
    
    //自定义ShortcutIcon
    // 如果设置了自定义的icon,那么系统自带的就不生效
    UIApplicationShortcutIcon *myGuestIcon = [UIApplicationShortcutIcon iconWithTemplateImageName:@"myGuestImage"];
    
    UIApplicationShortcutItem *myGuestItem = [[UIApplicationShortcutItem alloc] initWithType:@"myGuest" localizedTitle:@"我的客户" localizedSubtitle:nil icon:myGuestIcon userInfo:nil];
    
    [UIApplication sharedApplication].shortcutItems = @[addOpportunityItem,bookMarkItem,searchGuestItem,myGuestItem];
    
    return YES;
}

// 作用:点击快速启动项菜单上的某个快速启动项跳转到指定界面
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
// 方式一:type
    if ([shortcutItem.type isEqualToString:@"addOpportunity"]) {
        NSLog(@"点击了添加机会item");
    } else if ([shortcutItem.type isEqualToString:@"bookMark"]) {
        NSLog(@"点击了添加小记item");
    } else if ([shortcutItem.type isEqualToString:@"myGuest"]) {
        NSLog(@"点击了我的客户item");
    } else {
        NSLog(@"点击了搜索客户item");

    }

    // 方式二:title或者subtitle
    if ([shortcutItem.localizedTitle isEqualToString:@"添加机会"]) {
        NSLog(@"点击了添加机会item");
    } else if ([shortcutItem.localizedTitle isEqualToString:@"添加小记"]) {
        NSLog(@"点击了添加小记item");
    } else if ([shortcutItem.localizedTitle isEqualToString:@"我的客户"]) {
        NSLog(@"点击了我的客户item");
    } else {
        NSLog(@"点击了搜索客户item");
    }
}

注意:需要强调的是,快捷操作项最多定义4个。就像苹果官方的比喻一样:这里一共就有4个插槽,也就是最多显示4个快捷操作项。程序优先加载Info.plist文件中定义的静态快捷操作项,如果Info.plist中的静态快捷操作项不足4个才会去加载代码定义的动态快捷操作项(前提是代码中定义了动态快捷操作项)来补充剩余的插槽。

(二)app内的3D Touch - Peek and Pop

Snip20160418_9.png

启动并进入应用程序,在应用程序内可以响应用户不同的按压力度,随着用户按压力度的增大,程序会一次进入三个交互阶段:

  • 1.表明内容可以被预览
  • 2.展示预览视图-也就是我们所熟知的peek-并且带有可以配置的peek快捷操作项
  • 3.在预览视图上出现可选的导航视图-也即是我们所熟知的pop

app内的3D Touch主要分为peek可用性检测、Peek 、pop。peek 可用性主要是证明3D Touch的可用性。peek阶段只提供部分内容供用户预览,pop阶段显示整屏内容供用户浏览。从时间角度上看,peek阶段在前,pop阶段在后;从按压力度角度上看,peek所需的按压力度较小,pop阶段所需的按压力度较大。
拿新浪微博app上的图片微博为例,在iPhone6s以及iPhone6s Plus上,如果开启了3D Touch,当我们按压某条微博的某张图片时,当前被按压这张图片之外的所有内容都被系统自动模糊处理,以突出显示当前按压选中的内容,这代表peek是可用的,也就是说,轻轻按压图片以突出显示的这个过程是验证peek是否可用的过程。在此基础上不要松手,稍稍用力按压图片,就会在屏幕上弹出来一个圆角矩形的视图显示这张图片,这个阶段叫做peek阶段,也就是预览阶段。在此基础上不要松手,继续用力按压图片,就会在屏幕上弹出一个铺满整屏的视图显示这张图片,这个阶段叫做pop阶段。下面分别用四张图来说明不同阶段(不同按压力度)所呈现的界面效果。

阶段一:peek可用性

表明peek可用性.png

上图的效果苹果官方文档给出了如下解释:

peek可用性检测.png

表明peek的可用性
轻按时,周围内容出现模糊效果,告诉用户可以预览额外的内容—也就是peek是可用的。


阶段二:peek-预览部分内容

peek阶段.png
peek.png

稍微用力按压视图,然后视图会切换到一个peekView,这个peekView通常需要配置展示额外的内容。
如果用户手指抬起停止按压视图,peekView会自动消失并且app会恢复到交互开始之前的状态。


pop-全屏浏览内容

pop阶段.png

如果在peek阶段,用户手指没有抬起,而是继续用力按压视图以触发导航功能,用系统提供的pop转场到另一个视图:这个视图就是peek阶段peekView预览的视图——popView。popView会占满整个屏幕,只在导航条的左边显示一个返回按钮。


peek quick action

peek阶段上移peekView进行快捷操作.png

peek阶段,如果用户手指不离开屏幕,而是在屏幕上向上滑动,系统就会给用户显示事先已经关联好的快捷操作项。每一个peek的快捷操作项都是app内的一个深度链接。当快捷操作项被显示出来的时候,用户可以抬起手指停止触摸屏幕,并且此时peek View仍然会显示在屏幕上不会消失。这样就允许用户点击快捷操作项,进而调用相关的深度链接。

peek和pop功能的实现

如果要实现Peek和Pop需要进行下面两步配置:
1>在运行时检查3D Touch的可用性。
2>遵守UIViewControllerPreviewingDelegate协议,实现协议中的以下两个代理方法。
此处我以按压tableView内的cell为例讲解peek和pop的集成。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    static NSString *cellID = @"cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; 
    if (!cell) { 
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; 
    } 
    cell.textLabel.text = [NSString stringWithFormat:@"%ld",indexPath.row]; 
    // 检测3D Touch可用性
    if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) { 
    [self registerForPreviewingWithDelegate:self sourceView:cell]; 
    } 
    return cell;
}
/**
   *previewingContext:被预览的视图控制器的内容对象
   *location:源视图的左边系上的触摸点的坐标位置
   *调用时间:进入peek预览阶段时会调用这个方法。
   *作用:返回一个配置好的以供预览的视图控制器。
   *需要的操作:把源视图坐标系上的点转换为当前控制器的视图上的点。
   *返回一个控制器。
*/
- (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext
              viewControllerForLocation:(CGPoint)location
{
     //将触摸点的坐标转化为tableView坐标系上的坐标点
    CGFloat locationBaseTableView = [self.tableView convertPoint:location fromView:[previewingContext sourceView]]; 
    //根据触摸点获取触摸的cell的indexPath 
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
    // 根据indexPath对被peek的控制器进行配置 
    WSTableViewController *peekViewController =[[WSTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
    peekViewController.model = self.models[indexPath.row];
    return peekViewController;
}
/** 
   *previewingContext:被预览的视图控制器的内容对象 == 上面代理方法中的previewingContext,内存中是同一个对象
   *viewControllerToCommit:被present(pop)的视图控制器 == 上面代理方法中返回的控制器,内存中是同一个对象
   *调用时间:pop阶段调用这个方法
   *作用:配置并且present一个commit(pop)视图控制器。
*/
- (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext
     commitViewController:(UIViewController *)viewControllerToCommit
{
    [self.navigationController pushViewController:viewControllerToCommit animated:YES];
}

另外,如果希望实现peek Quick Actions功能需要在被peek的控制器内实现以下方法,在此例中,也就是需要在WSTableViewController中实现这个方法。

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

    UIPreviewAction *like = [UIPreviewAction actionWithTitle:@"喜欢" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
    NSLog(@"点击了喜欢");
}];

UIPreviewAction *comment = [UIPreviewAction actionWithTitle:@"评论" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
    NSLog(@"点击了评论");
}];

UIPreviewAction *complaint = [UIPreviewAction actionWithTitle:@"投诉" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
    NSLog(@"点击了投诉");
}];

    return @[like,comment,complaint];

名词解释

3D Touch可用性检查

想要检查某个设备运行时是否支持3F Touch,需要用到NSObject对象的trait environment的trait collection的forceTouchCapability属性。用户在app运行过程中可以关闭3D Touch,这需要借助于实现如下代理方法:traitCollectionDidChange:(UITraitCollection *)previousTraitCollection。可以看出,这个方法的参数就是UITraitCollection类型。那么这个方法什么时候调用呢?苹果官方是这么说的:
Called when the iOS interface environment changes.也就是说,当界面环境发生变化的时候回调用这个方法。
那么什么是界面环境呢?又怎么理解界面环境的变化呢?

UITraitEnvironment协议
/*! Trait environments expose a trait collection that describes their environment. */
@protocol UITraitEnvironment <NSObject>
@property (nonatomic, readonly) UITraitCollection *traitCollection NS_AVAILABLE_IOS(8_0);

/*! To be overridden as needed to provide custom behavior when the environment's traits change. */
- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection NS_AVAILABLE_IOS(8_0);
@end

UITraitEnvironment详解.png

大意如下:
UITraitEnvironment是声明在UIKit框架中的一个协议,继承自NSObject协议,起始于iOS8.0。界面环境包括水平和垂直方向的size class、呈现比例、以及用户界面语言(见文章末尾)。可以通过UITraitEnvironment协议来使用UITraitEnvironment。也就是说,iPhone和iPad设备的横屏和竖屏状态、缩放比例等都是界面环境。采用了UITraitEnvironment协议的类有:UIScreen、UIWindow、UIViewController、UIPresentationController和UIView。
一个采用了UITraitEnvironment协议的对象通过使用traitCollection属性来访问环境特征。同时这个协议也提供了一个可以重写的方法,当界面环境发生改变时以供系统调用。这个方法就是我们上面所说的 - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection。实现这个方法可以提高iOS app的自适应性。

访问trait collection
Snip20160415_3.png

trait collection 是视图控制器(UIViewController类或者其子类的实例)或者视图(UIView类或者其子类的实例)的属性,因为UIViewController和UIView默认遵守了UITraitEnvironment协议,trait collection正是UITraitEnvironment协议中声明的一个属性。

NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusEnvironment>
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIViewController : UIResponder <NSCoding, UIAppearanceContainer, UITraitEnvironment, UIContentContainer, UIFocusEnvironment>
响应界面环境的变化
响应界面环境的变化.png

大意如下:
调用时间:当该系统界面环境发生变化的时候会调用代理方法 - traitCollectionDidChange:
参数说明:其UITraitCollection类型的参数previousTraitCollection是界面环境发生变化之前的对象。
详细说明:
当iOS界面环境发生变化时,系统会调用这个代理方法。根据app实际需要,可以在视图控制器或者视图中实现这个代理方法,以响应一些改变。比如:当iPhone从竖屏旋转到横屏时,你也许需要调整控制器的子视图的布局,我们可以在这个方法中进行操作。默认这个方法的实现是空实现,这也就说明,我们需要重写这个代理方法的实现。
重写这个代理方法时,需要先调用父类的这个方法的默认实现,确保界面上的元素的视图层次结构先得到调整。使用如下类似的代码:

- (void) traitCollectionDidChange: (UITraitCollection *) previousTraitCollection {
    [super traitCollectionDidChange: previousTraitCollection];
    if ((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
        || self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass)) {
        // your custom implementation here
    }
}

注意:这个代理方法只有在iOS8.0及其之后的系统上调用。

实现协议方法

UIUserInterfaceIdiom - 用户界面语言

userInterfaceIdiom是UITraitCollection的属性。其属于UIUserInterfaceIdiom类型,UIUserInterfaceIdiom非对象性,其值是5个枚举值。分别如下:

typedef NS_ENUM(NSInteger, UIUserInterfaceIdiom) {
    UIUserInterfaceIdiomUnspecified = -1,
    UIUserInterfaceIdiomPhone NS_ENUM_AVAILABLE_IOS(3_2), // iPhone and iPod touch style UI
    UIUserInterfaceIdiomPad NS_ENUM_AVAILABLE_IOS(3_2), // iPad style UI
    UIUserInterfaceIdiomTV NS_ENUM_AVAILABLE_IOS(9_0), // Apple TV style UI
    UIUserInterfaceIdiomCarPlay NS_ENUM_AVAILABLE_IOS(9_0), // CarPlay style UI
};

文/VV木公子(简书作者)
PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载转载请联系作者获得授权,并注明出处,所有打赏均归本人所有!
如果您是iOS开发者或者,或者对本篇文章感兴趣,请关注本人,后续会更新更多相关文章!敬请期待!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 前言 关于这篇文章 由于iPhone 6S发布不到一年的时间,很多新特性、新技术还未普遍,不管是3D Touch的...
    Tangentw阅读 4,471评论 8 18
  • iOS 9之后提供以下几个3D Touch API: 1.Home screen quick action 主屏幕...
    DeadRoach阅读 747评论 1 1
  • 专著:http://www.jianshu.com/p/3443a3b27b2d 1.简单的介绍一下3D Touc...
    violafa阅读 1,009评论 1 0
  • 1.简单的介绍一下3D Touch 3D Touch的触控技术,被苹果称为新一代多点触控技术。其实,就是此前在Ap...
    Camille_chen阅读 12,040评论 19 33
  • 3D Touch 给iOS9的用户一个维度的交互。在支持的设备上,人们能够在主屏幕界面通过按压应用程序图标,快速的...
    Jack__yang阅读 585评论 0 2