欢迎关注微信公众号「随手记技术团队」,查看更多随手记团队的技术文章。
本文做者:丁同舟、王博文
原文连接:mp.weixin.qq.com/s/rP_ryWL2r…swift
iOS 11 已经发布了一段时间了,随手记团队也早早的完成了适配。在这里,咱们作了点总结,与你们一块儿分享一下,关于 iOS 11 一些新特性的适配。数组
iOS 11 中,官方提供了一种新的布局方法——经过 layout margins 进行布局。官方文档 Positioning Content Within Layout Margins 称,使用这种布局能够保证各个 content 之间不会相互覆盖。bash
总的来讲,layout margins 能够视做视图的内容和内容之间的空隙。它由每一个边的 insetValues
组成,分别是 top, bottom, leading and trailing. 对应的是上、下、左、右。微信
若是使用 Auto Layout 进行布局,并但愿约束遵循 layout margins,那么必需要在 Xcode 中打开 Constrain to margins
选项。这样,若是父视图的 layout margins 改变,那么全部绑定于父视图 margins 的子视图都会更新布局。app
注意iview
若是没有开启这个选项,那么全部创建的约束都会依赖于父视图的 bounds.异步
若是没有使用 Auto Layout, 而是经过设置 frame 布局的话,要遵循 layout margins 也并不困难,只须要在布局计算时使用 directionalLayoutMargins 这个属性。ide
var directionalLayoutMargins: NSDirectionalEdgeInsets { get set }复制代码
官方文档中阐述道,对于 view controller 的根视图,它的 directionalLayoutMargins
默认值是由 systemMinimumLayoutMargins和SafeAreaInsets 决定的。在 iPhone X 下打印根视图的这三个属性能够看到它们的关系。布局
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("SafeAreaInsets :" + "\(self.view.safeAreaInsets)")
print("systemMinimumLayoutMargins :" + "\(self.systemMinimumLayoutMargins)")
print("directionalLayoutMargins: " + "\(self.view.directionalLayoutMargins)")
// SafeAreaInsets :UIEdgeInsets(top: 88.0, left: 0.0, bottom: 34.0, right: 0.0)
// systemMinimumLayoutMargins :NSDirectionalEdgeInsets(top: 0.0, leading: 16.0, bottom: 0.0, trailing: 16.0)
// directionalLayoutMargins: NSDirectionalEdgeInsets(top: 88.0, leading: 16.0, bottom: 34.0, trailing: 16.0)
}复制代码
结果显而易见,directionalLayoutMargins
的默认值由 systemMinimumLayoutMargins
和 safeAreaInsets
组成。ui
注意
systemMinimumLayoutMargins
属性是否可用由 view controller 的布尔值属性viewRespectsSystemMinimumLayoutMargins
决定,默认为true
.
若是手动对 directionalLayoutMargins
赋值,那么在 viewRespectsSystemMinimumLayoutMargins
开启的状况下,系统会比较赋值后的 directionalLayoutMargins
和 systemMinimumLayoutMargins
,并取其较大值做为最终的 margins。
print("systemMinimumLayoutMargins :" + "\(self.systemMinimumLayoutMargins)")
print("origin directionalLayoutMargins: " + "\(self.view.directionalLayoutMargins)")
// 这里把 leading 和 trailing 分别赋值为相对于 systemMinimumLayoutMargins 的较大值20和较小值10
self.view.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 10)
print("assigned directionalLayoutMargins: " + "\(self.view.directionalLayoutMargins)")
// 打印日志可见只有 leading 的值改变为手动赋的值,trailing 依然遵循于 systemMinimumLayoutMargins
systemMinimumLayoutMargins :NSDirectionalEdgeInsets(top: 0.0, leading: 16.0, bottom: 0.0, trailing: 16.0)
origin directionalLayoutMargins: NSDirectionalEdgeInsets(top: 88.0, leading: 16.0, bottom: 34.0, trailing: 16.0)
assigned directionalLayoutMargins: NSDirectionalEdgeInsets(top: 88.0, leading: 20.0, bottom: 34.0, trailing: 16.0)复制代码
注意
若是不但愿受到
systemMinimumLayoutMargins
的影响,那么把 view controller 的viewRespectsSystemMinimumLayoutMargins
设为false
便可.
进入了 iOS 11,苹果为提供了更为漂亮和醒目的大标题的样式,若是想开启这样的功能,其实很简单。
只须要将 navigation bar 中的 prefersLargeTitles
置为 true
便可,这样便自动有了来自 iOS 11 中的大标题的样式。
self.navigationController.navigationBar.prefersLargeTitles = true复制代码
这里能够注意到,prefersLargeTitles
是配置在的 navigation controller 中的 navigation bar 中的。也就是说该 navigation controller 容器中的全部的 view controller 在此配置以后,都会受到影响。因此若是你要在当前的 navigation controller 中推入一个新的 view controller 的话,那么该 view controller 也会是大标题。所以为了不这个问题,UIKit 为 UINavigationItem
提供了 largeTitleDisplayMode
属性。
该属性默认为 UINavigationItem.LargeTitleDisplayMode.automatic
, 即保持与前面已经显示过的 navigation item 一致的样式。 若是想在后来的一个 view controller 避免大标题样式那么能够这么配置:
self.navigationItem.largeTitleDisplayMode = .never复制代码
除了大标题之外,在 iOS 11 中,UIKit 还为 navigation item 提供了便于管理搜索的接口。
具体参考以下:
self.navigationItem.searchController = self.searchController
self.navigationItem.hidesSearchBarWhenScrolling = true复制代码
在配置好你的 search controller 以后即可以直接提供给 navigation item 的 searchController
属性上,这样的便可以在导航栏看到一个漂亮的搜索框了。
此外还能够给 navigation item 中的属性 hidesSearchBarWhenScrolling
设置为 true
, 他可使你 view controller 中管理的 scroll view 在滑动的时候自动隐藏 search bar.
若是使用过 view controller 管理过 scroll view 的话,想必对 automaticallyAdjustsScrollViewInsets
这个属性必定不陌生。在 iOS 11 以前,该属性可让 view controller 自动管理 scroll view 中的 content inset. 可是,在实际在开发的过程当中,这样的自动管理的方式会带来麻烦,尤为是一些在 content inset 须要动态调整的状况。
为此,在 iOS 11, UIKit 废弃了 automaticallyAdjustsScrollViewInsets
属性,并将该的职责转移到 scroll view 自己。所以,在 iOS 11 中,为了解决这个问题,有两个 scroll view 的新属性。一个是用于管理调整 content inset 行为的属性 contentInsetAdjustmentBehavior
, 另外一个是获取调整后的填充的属性 adjustedContentInset
. 同时,UIScrollViewDelegate
也提供了新的代理方法,以方便开发者获取 inset 变化的时机:
optional func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView)复制代码
至此,对于这个「自动为开发者设置 inset」 的特性,苹果算是提供了至关完备的接口了。
不过做为开发者的咱们要注意的是,若是对本来自动设置 contentInset
属性的行为有依赖,在新的 iOS 11 的适配中,可能得作出调整。
此外,为了便于开发者在 scroll view 中使用 Auto Layout. UIKit 还提供了两个新的属性。一个是 contentLayoutGuide
, 它用来获取当前在 scroll view 内的内容的 layout guides. 而另外一个是 frameLayoutGuide
, 他用来获取实际内容的 layout guides. 这样说有点繁琐,仍是看 WWDC 的原图吧:
实际上对于 table view 而言,其最大的更新就在于新的特性 Drag and Drop 了吧。可是这个特性在适配中暂时不须要考虑,本文就不介绍了,让咱们一块儿来看看其余有意思的变化。
首先是在 iOS 11 中,table view 默认开启了 self-sizing, 能够注意到 estimatedRowHeight
, estimatedSectionHeaderHeight
以及 estimatedSectionFooterHeight
都被默认设置为 UITableViewAutomaticDimension
. 可是咱们知道,若是本来已经实现 tableView:heightForRowAtIndexPath:
之类的方法并返回了高度,那么在布局方面是不会有影响的,这对 iOS 11 适配而言是个好消息。
在 iOS 11 中,有了新的 layout margins 的概念,所以 UIKit 也为 separator inset 作了额外的补充。如今 separator inset 能够有两个来源,一个是从 cell 的边缘开始 (UITableViewSeparatorInsetReference.fromCellEdges
) ,另外一个是从 table view 默认的填充开始 (UITableViewSeparatorInsetReference.fromAutomaticInsets
)。其中,默认的填充由 table view 的 layout margins 进行控制。
此外,iOS 11 还为 table view 添加了更多的滑动操做的控制能力。分别能够经过如下两个 UITableViewDelegate
的方法实现:
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?复制代码
咱们能够注意到两个方法均要求返回一个 UISwipeActionsConfiguration
实例。为构造这个实例,咱们还须要构造一个由 UIContextualAction
实例组成的数组。UIContextualAction
与本来的 UITableViewRowAction
大体相似,可是要注意在 contextual action 的参数 handler 中,咱们须要调用 handler 参数中的 completionHandler 才能完成操做。从这一点咱们能够看到,彷佛在新的 action 中,咱们能够作一些异步操做相关的事情。
下面是一个删除操做的示例:
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let contextualAction = UIContextualAction.init(style: .destructive, title: "Delete") { (style, title, completionHandler) in
// 删除指定的数据
completionHandler(true)
}
let actionsConfiguration = UISwipeActionsConfiguration.init(actions: [contextualAction])
return actionsConfiguration
}复制代码
在 swipe actions configuration 中,咱们还须要注意一点,那就是新的 performsFirstActionWithFullSwipe
属性。经过开启这个属性的配置(默认开启),咱们能够为第一个动做提供 full swipe 操做 (一种经过过分滑动从而触发动做的交互) 。
若是仅仅实现了以往的编辑的代理方法,在 iOS 11 中,对于第一个动做将会默认支持 full swipe, 且不能关闭。
若是已经作过了 Touch ID 那么实际上适配 Face ID 便并不难了。即使是不作任何的改动,估计 Face ID 也是能够直接使用的(写做时, iPhone X 还未上市),固然相关的体验确定会打点折扣,毕竟文案以及相关的提示操做仍是在仅有 Touch ID 的前提下实现的。
与以往同样,能够经过 LAContext
类实现生物识别认证。不过须要注意的是,由于支持了新的 Face ID 认证,苹果便为 LAContext
类添加了新的接口 biometryType
用于区分 Touch ID 以及 Face ID。同时,以往仅涵盖 Touch ID 的错误类型,也在 iOS 11 中废弃了,相应的,苹果提供了新的更通用的错误类型予以替代。