翻译:iOS视图控制器编程指南(十三)——创建自适应界面(Bui
一个自适应界面应该响应trait和size变化。在视图控制器基本,使用trait大体上决定你所展示的内容和内容的布局。例如,当size类改变,你会选择改变视图属性,显示或隐藏视图,或者显示一组完全不同的视图。在做出这些重大决策,使用size变化来调整你的内容。
自适应trait变化
trait提供一种方式,可以针对不同的环境以不同的方式配置app,可以使用它们来粗略的调整界面。大部分情况下,可以直接在storyboard文件中修改trait,但是有些需要额外的代码。
配置storyboard处理不同size类
界面构建器使界面自适应不同size类变得更容易。storyboard编辑器支持配置不同的size类显示界面,删除视图的具体配置,指定不同布局约束。还可以创建图像asset为不同size类提供不同图像。使用这些工具意味着不需要在运行时以编程的方式改变。相反,当当前size类改变,UIKit自动更新界面。
图13-1展示了在界面构建器中用于配置界面的工具。size类查看控制改变界面视图的外观。使用该控制可以查看给定一个size类的界面外观。对于个人视图,使用安装控制来配置该视图是否存在给定size类配置。使用复选框左边的加号(+)按钮添加新的配置。
图13-1 针对不同size类自定义界面注意:未卸载的视图仍然在视图层级结构中,可以正常操作,但他们不会出现在屏幕上。
图片asset是存储应用图片资源的首选方法。每个图片asset包含相同图片的多个版本,每个版本用于特定配置。除了为标准和Retina显示指定不同的图片,还可以为不同水平和垂直size类指定不同图片。当配置了图片asset,UIImageView对象自动根据当前size类和分辨率选择图片。
图13-2 展示了图片asset属性。改变宽度和高度属性会在图片目录中添加slot,为每个size类对应的slot添加图片。
图13-2 为不同size类配置图片 asset改变子视图控制器的trait
子视图控制器默认继承他们父视图控制器的trait。如size类,使每个子视图控制器与父视图控制器有相同的trait没有意义。例如,在普通环境中的视图控制器可能希望给一个或多个子视图分配一个紧凑的size类,减少子视图的空间。当实现一个容器视图控制器,通过调用容器视图控制器的setOverrideTraitCollection:forChildViewController:方法修改子视图的trait。
列表13-1展示了如何创建一组新的trait,并将其关联到子视图控制器。只需在父视图控制器中执行一次该代码。覆盖子视图的trait直到再次改变trait或者从视图控制器层级结构中删除trait。
列表13-1 改变子视图控制器的trait
<pre><code>
UITraitCollection* horizTrait = [UITraitCollection
traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
UITraitCollection* vertTrait = [UITraitCollection
traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact];
UITraitCollection* childTraits = [UITraitCollection
traitCollectionWithTraitsFromCollections:@[horizTrait, vertTrait]];
[self setOverrideTraitCollection:childTraits
forChildViewController:self.childViewControllers[0]];
</pre></code>
当改变父视图控制器的trait,子视图继承任何没有显式覆盖父类的trait。例如,当父视图控制器的水平size类从常规变成紧凑,在前面例子中的视图控制器保持其常规水平size类。然而,如果displayScale trait改变,子视图继承新值。
presented视图控制器自适应新样式
presented视图控制器在水平常规和紧凑环境中自适应。当从水平常规环境过渡到水平紧凑环境时,UIKit默认改变内置present风格为 UIModalPresentationFullScreen。对于自定义present风格,present控制器可以决定适应行为并根据情况调整。
对于一些app,采用全屏风格可能存在问题。例如,通常点击其他区域,弹窗dismiss,但在紧凑环境中不可能这样做,弹窗覆盖整个屏幕,如图13-3所示。当默认自适应风格不合适,可以告诉UIKit使用不同的风格或者present一个完全不同视图控制器,使之更适合全屏风格。
图13-3 在常规和紧凑环境中的弹窗要改变present风格的默认自适应行为,分配 delegate 到相关present控制器。使用presented视图控制器的 presentationController 属性访问presentation控制器。presentation控制器在做出自适应相关更改前查询代理对象。代理可以返回不同Presentation风格,而不是默认的。代理可以提供Presentation控制器另一个要显示的视图控制器。
使用代理的adaptivePresentationStyleForPresentationController: 方法指定不同presentation风格而不是默认风格。当过渡到紧凑环境时,仅支持的样式是两种全屏风格或UIModalPresentationNone。返回UIModalPresentationNone告诉Presentation控制器忽视紧凑环境并继续使用前面的Presentation风格。如果是弹窗,忽视变更,提供类似iPad上的弹窗。在图13-4中展示了默认全屏自适应和不适应,这样你可以比较present结果。
图13-4 改变presented视图控制器的自适应行为要完全取代视图控制器,实现代理的presentationController:viewControllerForAdaptivePresentationStyle: 方法。当自适应紧凑环境时,使用该方法插入导航控制器到视图层级结构或者加载为小空间设计的视图控制器。
实现自适应popover的提示
当从水平常规变为水平紧凑环境时,弹窗需要额外的修改。水平紧凑默认将弹窗改为全屏present。通常通过点击弹窗外区域dismiss弹窗,当变为全屏present时无法dismiss弹窗。可以通过以下方式实现:
- push弹窗的视图控制器到存在的导航堆栈上。当父导航控制器可用时,dismiss弹窗并将其视图控制器push到导航堆栈上。
- 当全屏时,添加控件dismiss弹窗。可以添加控件到弹窗的视图控制器上,但更好的选择是使用presentationController:viewControllerForAdaptivePresentationStyle: 方法将弹窗从导航控制器中移除。使用导航控制器,则有一个模态界面和空间可以添加完成按钮或其他控件dismiss内容。
- 使用present控制器代理消除任何自适应变化。获取弹窗present控制器并分配代理实现adaptivePresentationStyleForPresentationController:方法。该方法返回 UIModalPresentationNone,导致弹窗继续显示为一个弹窗。更多信息,参见presented视图控制器适应新风格(Adapting Presented View Controllers to a New Style)。
响应size变化
size发生变化的原因有很多,包括以下几点:
- 底层窗口的尺寸改变,通常因为方向改变。
- 父视图控制器调整子视图控制器的size。
- present控制器改变presented视图控制器的size。
当size发生变化时,UIKit通过正常布局过程,自动更新可见视图控制器的size和position。如果使用Auto Layout约束指定视图的size和position,app自适应任何size变化并运行在不同屏幕尺寸的设备上。
如果Auto Layout约束不能满足你的要求,可以使用viewWillTransitionToSize:withTransitionCoordinator:方法改变布局。还可以使用该方法创建额外动画。例如,在界面旋转过程中,使用过度协调器的targetTransform 属性来创建界面的逆转矩阵。