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來源:簡書著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。