iOS 7 以后苹果给 UIViewController 引入了 topLayoutGuide 和 bottomLayoutGuide 两个属性来描述不但愿被透明的状态栏或者导航栏遮挡的最高位置(status bar, navigation bar, toolbar, tab bar 等)。这个属性的值是一个 length 属性( topLayoutGuide.length)。 这个值可能由当前的 ViewController 或者 NavigationController 或者 TabbarController 决定。swift
iOS 11 开始弃用了这两个属性, 而且引入了 Safe Area 这个概念。苹果建议: 不要把 Control 放在 Safe Area 以外的地方app
// These objects may be used as layout items in the NSLayoutConstraint API @available(iOS, introduced: 7.0, deprecated: 11.0) open var topLayoutGuide: UILayoutSupport { get } @available(iOS, introduced: 7.0, deprecated: 11.0) open var bottomLayoutGuide: UILayoutSupport { get }
今天, 来研究一下 iOS 11 中新引入的这个 API。ide
iOS 11 中 UIViewController 的 topLayoutGuide 和 bottonLayoutGuide 两个属性被 UIView 中的 safe area 替代了。布局
@available(iOS 11.0, *) open var safeAreaInsets: UIEdgeInsets { get } @available(iOS 11.0, *) open func safeAreaInsetsDidChange()
safeAreaInsets测试
这个属性表示相对于屏幕四个边的间距, 而不只仅是顶部还有底部。这么说好像没有什么感受, 咱们来看一看这个东西分别在 iPhone X 和 iPhone 8 中是什么样的吧!优化
什么都没有作, 只是新建了一个工程而后在 Main.storyboard
中的 UIViewController 中拖了一个橙色的 View 而且设置约束为:ui
在 ViewController.swift
的 viewDidLoad
中打印spa
override func viewDidLoad() { super.viewDidLoad() print(view.safeAreaInsets) } // 不管是iPhone 8 仍是 iPhone X 输出结果均为 // UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
这样对比能够看出, iPhone X 同时具备上下, 还有左右的 Safe Area。3d
**再来看这个例子: ** 拖两个自定义的 View, 这个 View 上有一个 显示不少字的Label。而后设置这两个 View 的约束分别是:code
let view1 = MyView() let view2 = MyView() view.addSubview(view1) view.addSubview(view2) let screenW = UIScreen.main.bounds.size.width let screenH = UIScreen.main.bounds.size.height view1.frame = CGRect( x: 0, y: 0, width:screenW, height: 200) view2.frame = CGRect( x: 0, y: screenH - 200, width:screenW, height: 200)
能够看出来, 子视图被顶部的刘海以及底部的 home 指示区挡住了。咱们可使用 frame 布局或者 auto layout 来优化这个地方:
let insets = UIApplication.shared.delegate?.window??.safeAreaInsets ?? UIEdgeInsets.zero view1.frame = CGRect( x: insets.left, y: insets.top, width:view.bounds.width - insets.left - insets.right, height: 200) view2.frame = CGRect( x: insets.left, y: screenH - insets.bottom - 200, width:view.bounds.width - insets.left - insets.right, height: 200)
这样起来好多了, 还有另一个更好的办法是直接在自定义的 View 中修改 Label 的布局:
override func layoutSubviews() { super.layoutSubviews() if #available(iOS 11.0, *) { label.frame = safeAreaLayoutGuide.layoutFrame } }
这样, 不只仅是在 ViewController 中可以使用 safe area 了。
在 iOS 11 中 UIViewController 有一个新的属性
@available(iOS 11.0, *) open var additionalSafeAreaInsets: UIEdgeInsets
当 view controller 的子视图覆盖了嵌入的子 view controller 的视图的时候。好比说, 当 UINavigationController 和 UITabbarController 中的 bar 是半透明(translucent) 状态的时候, 就有 additionalSafeAreaInsets
// UIView @available(iOS 11.0, *) open func safeAreaInsetsDidChange() //UIViewController @available(iOS 11.0, *) open func viewSafeAreaInsetsDidChange()
这两个方法分别是 UIView 和 UIViewController 的 safe area insets 发生改变时调用的方法,若是须要作一些处理,能够重写这个方法。有点相似于 KVO 的意思。
额外的 safe area insets 也能用来测试你的 app 是否支持 iPhone X。在没有 iPhone X 也不方便使用模拟器的时候, 这个仍是颇有用的。
//竖屏 additionalSafeAreaInsets.top = 24.0 additionalSafeAreaInsets.bottom = 34.0 //竖屏, status bar 隐藏 additionalSafeAreaInsets.top = 44.0 additionalSafeAreaInsets.bottom = 34.0 //横屏 additionalSafeAreaInsets.left = 44.0 additionalSafeAreaInsets.bottom = 21.0 additionalSafeAreaInsets.right = 44.0
在 scroll view 上加一个 label。设置scroll 的约束为:
scrollView.snp.makeConstraints { (make) in make.edges.equalToSuperview() }
iOS 7 中引入 UIViewController 的 automaticallyAdjustsScrollViewInsets 属性在 iOS11 中被废弃掉了。取而代之的是 UIScrollView 的 contentInsetAdjustmentBehavior
@available(iOS 11.0, *) public enum UIScrollViewContentInsetAdjustmentBehavior : Int { case automatic //default value case scrollableAxes case never case always } @available(iOS 11.0, *) open var contentInsetAdjustmentBehavior: UIScrollViewContentInsetAdjustmentBehavior
never 不作调整。
scrollableAxes content insets 只会针对 scrollview 滚动方向作调整。
always content insets 会针对两个方向都作调整。
automatic 这是默认值。当下面的条件知足时, 它跟 always 是一个意思
在其余状况下 automoatc 跟 scrollableAxes 同样
iOS 11 中 UIScrollView 新加了一个属性: adjustedContentInset
@available(iOS 11.0, *) open var adjustedContentInset: UIEdgeInsets { get }
adjustedContentInset 和 contentInset 之间有什么区别呢?
在同时有 navigation 和 tab bar 的 view controller 中添加一个 scrollview 而后分别打印两个值:
//iOS 10 //contentInset = UIEdgeInsets(top: 64.0, left: 0.0, bottom: 49.0, right: 0.0) //iOS 11 //contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0) //adjustedContentInset = UIEdgeInsets(top: 64.0, left: 0.0, bottom: 49.0, right: 0.0)
而后再设置:
// 给 scroll view 的四个方向都加 10 的间距 scrollView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
打印:
//iOS 10 //contentInset = UIEdgeInsets(top: 74.0, left: 10.0, bottom: 59.0, right: 10.0) //iOS 11 //contentInset = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) //adjustedContentInset = UIEdgeInsets(top: 74.0, left: 10.0, bottom: 59.0, right: 10.0)
因而可知,在 iOS 11 中 scroll view 实际的 content inset 能够经过 adjustedContentInset 获取。这就是说若是你要适配 iOS 10 的话。这一部分的逻辑是不同的。
系统还提供了两个方法来监听这个属性的改变
//UIScrollView @available(iOS 11.0, *) open func adjustedContentInsetDidChange() //UIScrollViewDelegate @available(iOS 11.0, *) optional public func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView)
咱们如今再来看一下 UITableView 中 safe area 的状况。咱们先添加一个有自定义 header 以及自定义 cell 的 tableview。设置边框为 self.view 的边框。也就是
tableView.snp.makeConstraints { (make) in make.edges.equalToSuperview() }
或者
tableView.frame = view.bounds
自定义的 header 上面有一个 lable,自定义的 cell 上面也有一个 label。将屏幕横屏以后会发现,cell 以及 header 的布局均自动留出了 safe area 之外的距离。cell 仍是那么大,只是 cell 的 contnt view 留出了相应的距离。这实际上是 UITableView 中新引入的属性管理的:
@available(iOS 11.0, *) open var insetsContentViewsToSafeArea: Bool
insetsContentViewsToSafeArea
的默认值是 true, 将其设置成 no 以后:
能够看出来 footer 和 cell 的 content view 的大小跟 cell 的大小相同了。这就是说:在 iOS 11 下, 并不须要改变 header/footer/cell 的布局, 系统会自动区适配 safe area
须要注意的是, Xcode 9 中使用 IB 拖出来的 TableView 默认的边框是 safe area 的。因此实际运行起来 tableview 都是在 safe area 以内的。
咱们在作一个相同的 collection view 来看一下 collection view 中是什么状况:
这是一个使用了 UICollectionViewFlowLayout
的 collection view。 滑动方向是竖向的。cell 透明, cell 的 content view 是白色的。这些都跟上面 table view 同样。header(UICollectionReusableView) 没有 content view 的概念, 因此给其自身设置了红色的背景。
从截图上能够看出来, collection view 并无默认给 header cell footer 添加safe area 的间距。可以将布局调整到合适的状况的方法只有将 header/ footer / cell 的子视图跟其 safe area 关联起来。跟 IB 中拖 table view 一个道理。
如今咱们再试试把布局调整成更像 collection view 那样:
截图上能够看出来横屏下, 左右两边的 cell 都被刘海挡住了。这种状况下, 咱们能够经过修改 section insets 来适配 safe area 来解决这个问题。可是再 iOS 11 中, UICollectionViewFlowLayout 提供了一个新的属性 sectionInsetReference 来帮你作这件事情。
@available(iOS 11.0, *) public enum UICollectionViewFlowLayoutSectionInsetReference : Int { case fromContentInset case fromSafeArea case fromLayoutMargins } /// The reference boundary that the section insets will be defined as relative to. Defaults to `.fromContentInset`. /// NOTE: Content inset will always be respected at a minimum. For example, if the sectionInsetReference equals `.fromSafeArea`, but the adjusted content inset is greater that the combination of the safe area and section insets, then section content will be aligned with the content inset instead. @available(iOS 11.0, *) open var sectionInsetReference: UICollectionViewFlowLayoutSectionInsetReference
能够看出来,系统默认是使用 .fromContentInset
咱们再分别修改, 看具体会是什么样子的。
这种状况下 section content insets 等于原来的大小加上 safe area insets 的大小。
跟使用 .fromLayoutMargins
类似使用这个属性 colection view 的 layout margins 会被添加到 section content insets 上面。
前面的例子都说的是用代码布局要实现的部分。可是不少人都仍是习惯用 Interface Builder 来写 UI 界面。苹果在 WWDC 2107 Session 412 中提到:Storyboards 中的 safe area 是向下兼容的 也就是说, 即便在 iOS10 及如下的 target 中,也可使用 safe area 来作布局。惟一须要作的就是给每一个 stroyboard 勾选 Use Safe Area Layout Guide。实际测试看,应该是 iOS9 之后都只须要这么作。
知识点: 在使用 IB 设置约束以后, 注意看相对的是 superview 仍是 topLayoutGuide/bottomLayoutGuide, 包括在 Xcode 9 中勾选了 Use Safe Area Layout Guide 以后,默认应该是相对于 safe area 了。
if iPhoneX{}
只会给以后的工做代码更多的麻烦。做者:CepheusSun连接:http://www.jianshu.com/p/63c0b6cc66fd來源:简书著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。