Session 235: UIKit: Apps for Every Size and Shape
作者简介:@Hale 丁香园 iOS 工程师
现如今苹果的移动设备已经不像初代的时候只有一种分辨率尺寸。iOS12 支持包括 iPhone5s、iPhone8、iPhone8 Plus、iPhone X、iPad 等各种尺寸的设备。相信一定有许多开发者对多设备的适配开发有过困扰,本 Session 对快速适配所有型号 iOS 移动设备的开发方法进行了介绍。下面介绍的属性和方法可以让开发者用最短的时间让开发项目适配苹果全系列移动设备,同时还保证了用户体验不会受到影响。
Safe area and layout margins(安全区域和布局边距)
安全区域
Safe Area
在 iOS11 中被提出,它是一个非常重要的属性。相信大多数开发者对这个属性已经并不陌生,Safe Area
的提出主要是为了适配像 iPhone X 一样的全面屏。我们可以通过 UIView
的 safeAreaInsets
和 safeAreaLayoutGuide
属性来确定安全区域,同时安全区域限制了视图的可见部分,如 图1 所示。
安全区域具有传递性
通过下面两张图我们可以知道,最底部视图A的安全区域如 图2 所示,随后在上添加一个不受限于视图A安全区域的浅灰色视图B,然后在视图B上添加子视图C,并设置视图C的约束依赖于视图B的 safeAreaLayoutGuide
,视图C的可视范围会被限制在 图3 黄色区域,我们可以得出父视图的安全区域会向上传递,如 图2、3 所示。
可扩展安全区域
UIViewController
支持使用 additionalSafeAreaInsets
属性,自定义扩展安全区域大小,以满足一些应用场景。此外 UIView
提供了 safeAreaInsetsDidChange()
方法,UIViewController
提供了 viewSafeAreaInsetsDidChange()
方法,用于检测安全区域的改变。当视图的安全区域发生改变,对应的方法就会被调用,如 图4 所示。
布局边距
iOS8 中提出了 layoutMargins
的概念,其主要用于设置子视图与父视图之间的边距,在 iOS11 中新增了 directionalLayoutMargins
,主要为了 Right To Left(RTL)语言下可以进行自动适配。默认情况下,layoutMargin
到各边的距离是8个点。通过在 Interface Builder
里面勾选 Constrain to margins
它会根据版本在 iOS11 及以上的系统中自动使用 directionalLayoutMargins
,如 图5、6 所示。
安全区域和布局边距协同作用
正常情况下子视图的布局边距会依赖父视图的安全区域,但是当设置了insetsLayoutMarginsFromSafeArea = false
之后,子视图可以达到突破父视图安全区域的布局效果,如 图7、8 所示。
布局之子视图传播
当一个视图的preservesSuperviewLayoutMargins
属性为 true 时,在对它的子视图进行布局时,父视图的 margin 也会被考虑在内。如果存在一个子视图的 frame 刚好和父视图的 margin 表示区域有重合,此时设置 preservesSuperviewLayoutMargins
为 true ,则子视图会被刚好限制在父视图的 margin 内,如 图9、10 所示。
最小边距
UIViewController
存在属性 systemMinimumLayoutMargins
,可以对其进行重写,默认情况下 view 的布局边距会受这个属性的返回值制约。如下重写了该属性,则 view 的边距最小会为 [20,20,20,20]。
override var systemMinimumLayoutMargins: NSDirectionalEdgeInsets {
return NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
}
若设置 viewRespectsSystemMinimumLayoutMargins
为 false,则 view 布局边距不受 systemMinimumLayoutMargins
属性的影响,默认为 [8,8,8,8]。
Scroll views
adjustedContentInset
iOS11 中提出了 UIScrollView
的新属性 adjustedContentInset
,它的值等于 UIScrollView
原有的 contentInset
加上 安全区域等 system inset
,如 图11 所示。
adjustedContentInset = contentInset + system inset
图11
废弃 Automatic Content Inset
本 Session 再次提到 iOS11 之后废除了原有的 UIViewController
属性 automaticallyAdjustsScrollViewInsets
取而代之的是 UIScrollView
的新增枚举 ContentInsetAdjustmentBehavior
,该枚举结构如下:
public enum ContentInsetAdjustmentBehavior : Int {
case automatic
case scrollableAxes
case never
case always
}
如下图如果设置枚举值为 .always
,则默认情况下 scrollView
的 adjustedContentInset
就等于 safeAreaInsets
,即可视区域不会被 navigationBar
和 tabBar
遮挡,如 图12 所示。
如果将枚举值设置为 .scrollableAxes
,则在可以滚动的方向上,或者设置了 alwaysBounceHorizontal/Vertical
为 true 的时候,Inset 才会生效。如 图14,页面内容比较少的时候,垂直方向上 scrollView
不可滚动,导致文本标题部分被 navigationBar
遮挡,如 图13、14 所示。
系统默认设置的枚举值是 .automatic
, 这个枚举值基本和 .scrollableAxes
表现一致,但唯一不同的是它还秉承了原来 automaticallyAdjustsScrollViewInsets = true
的特性,在有 navigationBar
且 isTranslucent
为 true 时,即使垂直方向上不能够滚动,依然能够调整 Inset 使内容可见,如 图15 所示。
如果将枚举值设置为 .nerver
,则 scrollView
的 Inset 不会受 safeAreaInserts
的影响而改变、如 图16 所示。
编写自适应的应用程序
隐藏 status bar
若果在一些场景下需要隐藏 status bar
,我们一般会这么做:
class ArticleViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
return true
}
}
不幸的是在 iPhone X 上面这么写是无效的,在 iPhone X 上面只有在隐藏了 navigationBar
的前提下,上面这段代码才会生效,所以苹果官方给出的建议是同时隐藏 navigationBar
和 status bar
,如 图17 所示。
readableContentGuide & cellLayoutMarginsFollowreadableWidth
iOS9 就提出了 readableContentGuide
这一概念,主要是用于一些阅读类应用,在可视宽度较大的时候,希望能够通过布局将阅读区域限定在一定范围,已缓解用户阅读的过程中追踪内容移动头部所造成的疲劳。readableContentGuide
的间距大小会随着字体大小、设备不同等因素而发生改变。现这一属性同样兼容 safeAreaInsets
。同样的 UITableView
的 cellLayoutMarginsFollowreadableWidth
属性也同样兼容 safeAreaInsets
,如 图18、19、20 所示。
insetsContentViewToSafeArea
UITableView
在iOS11开始添加了一个新的属性insetsContentViewsToSafeArea
,该属性能够控制 TableViewCell
的 ContentView
是否被 safeAreaInsets
所影响,如 图20、21 所示。
底部按钮布局最佳实践
iPhone X 之后,我们在开发过程中经常会遇到如何布局底部按钮的问题。在本 Session 中官方给出了一种方案,例如设置按钮距底部相对于 superView 的约束为16,约束的 Priority
为 999,同时设置按钮底部相对于 safeAreaLayoutGuide
的约束值为大于等于 0。即可实现按钮在 iphone X 和 其他设备上的不同布局,如 图23 所示。
总结
其实本 Session 并没有提出任何新的属性和方法,最新的属性在 iOS11 SDK 中就已经提出来了。可能很多开发者,在适配iPhone X 的时候遇到的问题也都解决的差不多了。但个人认为这个 Session 还是很有必要的,它将现有的用于适配开发的 UIKit SDK 进行了归纳总结,这将有助于开发者进一步了解这些属性之间的关联关系对快速适配多种尺寸设备的项目开发会有很大帮助。
查看更多 WWDC 18 相关文章请前往 老司机x知识小集xSwiftGG WWDC 18 专题目录