
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
複製程式碼

廢棄 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 專題目錄