原文出自我在團隊技術公眾號發表的 《隨手記在iPhone X上的真機適配實踐總結》
Intro
前幾天,隨手記提交了包含 iPhone X 適配內容的版本提交了 App Store,宣告了隨手記 App 對 iPhone X 緊張的適配工作暫時告一段落。從最初在 iPhone X 模擬器上面運行時一塌糊涂的界面,到最后真機測試時基本可以完全適配。在這一路過來適配的歷程中,我們整理出下面這份通用的 iPhone X 適配文檔供大家參考。
Always Remember
摘自 Designing for iPhone X : 為了讓你的應用在 iPhone X上 面完美運行,你需要將可視元素擴展至填充整個展示窗口(屏幕)上,同時,你也需要保證如按鈕、Tab Bar 等可交互控件,以及一些至關重要的信息不會因為屏幕的圓角而被裁掉,或者被手機的“劉海”和虛擬“Home鍵”遮住。
Updating your App to work on the new display(iPhone X) involves extending visual elements to fill the display's view port, while also keeping controls and critical infomation from getting clipped in the corners or covered by the sensor housing or home indicator.
UIKit and Auto Layout
對于原生的 UIKit
控件來說,適配 iPhone X 是一件非常輕松的事。假如你的英語使用了許多原生控件并且沒有對它們做過多的自定義的話,例如像導航條 (Navigation Bars),列表 (Tables),集合視圖 (Collection Views),這些控件會在 iPhone X 屏幕上自動調整其布局。
如上圖,在 iPhone X 屏幕上面,Navigation bar 和底部 tab bar 分別在其控件的頂部和底部作了延伸,為“劉海”和 Home Indicator 留出位置。
對于使用了 Auto Layout 的控件而言,適配 iPhone X 的特殊屏幕也不會成為難題。在 iOS 11 中,蘋果引入了一種新的 UILayoutGuide
解決了適配問題
var safeAreaLayoutGuide: UILayoutGuide { get }
safe area 的特性,為我們適配 iPhone X 打下了基礎。
Safe Area
在蘋果的官方 [Human Interface Guidelines] (https://developer.apple.com/ios/human-interface-guidelines/overview/iphone-x/) 中,對 safe area 的描述為
All apps should adhere to the safe area and layout margins defined by UIKit, which ensure appropriate insetting based on the device and context
The safe area also prevents content from underlapping the status bar, navigation bar, toolbar, and tab bar.
所有的應用應該附著于安全區域和 UIKit 定義的邊距中,這可以讓應用在各種設備或者橫豎屏情況下有正確的布局。同時,安全區域可以保證頁面的內容不會和狀態條、導航條、工具條或者底部導航重疊。
另一官方文檔 Positioning Content Relative to the Safe Area 中的一張圖清晰地展示了 safe areas 在應用中代表的意義。
Auto Layout with Safe Area
Standard system-provided views automatically adopt a safe area layout guide.
New in iOS 11, Apple is deprecating the top and bottom layout guides and replacing them with a single safe area layout guide:
iOS 11中,蘋果推出了 SafeAreaLayoutGuide 取代了 bottomLayoutGuide
和 topLayoutGuide
,對于已經使用了 bottomLayoutGuide
和 topLayoutGuide
的布局來說,單純使用 safeAreaLayoutGuide
可以完成一樣的工作,同時也可以完美適配 iPhone X 的布局。
使用 Auto Layout 布局,適配 safe area 會是一個非常簡單的事情。打開一個 Storyboard,會發現視圖層次中多了一個 Safe Area
所以,使用 Auto Layout 布局的話,safe area 就相對于一個處于每個 view controller 中處于視圖層級底層的容器,我們的子控件只需要和它建立約束關系,就可以完美適配 iPhone X 安全區域
Programming with Safe Area
如果使用代碼進行布局,對于 safe area 適配也不會復雜。iOS 11 中 UIView
的新 API SafeAreaInsets
和 UIViewController
的新 API additionalSafeAreaInsets
能夠很方便地解決代碼布局的適配問題。
var safeAreaInsets: UIEdgeInsets { get }
You might use this property at runtime to adjust the position of your view's content programmatically.
Example:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let insets = self.view.safeAreaInsets
self.bottomButton.frame = CGRect(x: 0, y: self.view.frame.size.height - 80 - insets.bottom, width: self.view.frame.size.width, height: 80)
}
另一個新的 API additionalSafeAreaInsets
則提供給開發人員能夠自主擴展 SafeArea 區域的能力。顧名思義,如果對這個屬性主動賦值,那么整個視圖的 SafeArea 便會發生變化。
var additionalSafeAreaInsets: UIEdgeInsets { get set }
Use this property to adjust the safe area insets of this view controller's views by the specified amount. The safe area defines the portion of your view controller's visible area that is guaranteed to be unobscured by the system status bar or by an ancestor-provided view such as the navigation bar.
You might use this property to extend the safe area to include custom content in your interface. For example, a drawing app might use this property to avoid displaying content underneath tool palettes.
Example
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("Origin SafeAreaInsets :" + "\(self.view.safeAreaInsets)")
self.additionalSafeAreaInsets = UIEdgeInsetsMake(5, 5, 5, 5)
print("view controller's additionalSafeAreaInsets set to " + "\(self.additionalSafeAreaInsets)")
print("Adjusted SafeAreaInsets :" + "\(self.view.safeAreaInsets)")
}
控制臺輸出如下
Origin SafeAreaInsets :UIEdgeInsets(top: 88.0, left: 0.0, bottom: 34.0, right: 0.0)
view controller's additionalSafeAreaInsets set to UIEdgeInsets(top: 5.0, left: 5.0, bottom: 5.0, right: 5.0)
Adjusted SafeAreaInsets :UIEdgeInsets(top: 93.0, left: 5.0, bottom: 39.0, right: 5.0)
需要注意,在不同狀態,例如豎屏和橫屏下,獲取到的 SafeAreaInsets
是不同的,對于 ViewController
來說,大概符合以下的規則:
//豎屏
SafeAreaInsets = (top: heightForTopBars + additionalSafeAreaInsets.top,
left: 0 + additionalSafeAreaInsets.left,
bottom: heightForBottomBars + additionalSafeAreaInsets.bottom,
right: 0 + additionalSafeAreaInsets.right)
//橫屏
SafeAreaInsets = (top: heightForTopBars + additionalSafeAreaInsets.top,
left: StatusBarHeight + additionalSafeAreaInsets.left,
bottom: HomeIndicatorAreaHeight + additionalSafeAreaInsets.bottom,
right: StatusBarHeight + additionalSafeAreaInsets.right)
SafeAreaInsets 調用時機問題
If the view is not currently installed in a view hierarchy, or is not yet visible onscreen, the edge insets in this property are 0.
時刻記住,SafeAreaInsets
并不是隨時都能獲取到的。 Apple 官方的解釋是在視圖顯示在屏幕上或者裝載到一個視圖層級中的時候,才能正確獲取到 SafeAreaInsets
,否則返回0。
例如在一個 ViewController
的生命周期中追蹤 self.view.safeAreaInsets
得到的結果:
ViewController loadView() SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
ViewController viewDidLoad() SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
ViewController viewWillAppear() SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
ViewController viewDidLayoutSubviews() SafeAreaInsets :UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)
ViewController viewDidAppear() SafeAreaInsets :UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)
控制器根視圖
For the view controller's root view, the insets account for the status bar, other visible bars, and any additional insets that you specified using the additionalSafeAreaInsets property of your view controller.
對于 view controller的根視圖,SafeAreaInsets
會根據各種 bar 的高度,以及開發者自己設置的 additionalSafeAreaInsets
屬性來計算。
其他
For other views in the view hierarchy, the insets reflect only the portion of the view that is covered. For example, if a view is entirely within the safe area of its superview, the edge insets in this property are 0.
對于其他的視圖,官方的的說法是只有當視圖存在一部分被非安全區域遮擋的情況下,SafeAreaInsets
才會返回相應的值。如果整個視圖已經處于安全區域中,那么 SafeAreaInsets
返回0。
Blue View SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
Red View SafeAreaInsets :UIEdgeInsets(top: 88.0, left: 0.0, bottom: 0.0, right: 0.0)
Home Indicator
Home Indicator 的設置類似于 prefersStatusBarStyle
,iOS 11 增加了 UIViewController
的一個 UIHomeIndicatorAutoHidden
分類來控制 Home 鍵的自動隱藏。通常在全屏播放視頻,全屏游戲等場景下會需要用到此特性。
@interface UIViewController (UIHomeIndicatorAutoHidden)
// Override to return a child view controller or nil. If non-nil, that view controller's home indicator auto-hiding will be used. If nil, self is used. Whenever the return value changes, -setNeedsHomeIndicatorAutoHiddenUpdate should be called.
- (nullable UIViewController *)childViewControllerForHomeIndicatorAutoHidden API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos);
// Controls the application's preferred home indicator auto-hiding when this view controller is shown.
- (BOOL)prefersHomeIndicatorAutoHidden API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos);
// This should be called whenever the return values for the view controller's home indicator auto-hiding have changed.
- (void)setNeedsUpdateOfHomeIndicatorAutoHidden API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos);
@end
iPhone X: Dealing with Home Indicator
需要注意 prefersHomeIndicatorAutoHidden
,蘋果官方并不確保這個重寫這個方法一定可以讓 Home Indicator 自動隱藏。
Discussion
Override this method to signal your preference for displaying the visual indicator. The system takes your preference into account, but returning true is no guarantee that the indicator will be hidden.
值得一提的是,蘋果并沒有提供可以手動改變 Home Indicator 顏色的接口給開發者。我們從與蘋果員工的交流中得知,蘋果官方并不希望我們手動更改 Home Indicator 的顏色,應該遵循其自動切換顏色的特性(Home Indicator 會根據底色的不同自動切換黑色和白色)。
其他
Some Height
Item | Normal Height | iPhoneX Height |
---|---|---|
UINavigationBar | 64px | 88px |
UIStatusBar | 20px | 44px |
UITabBar | 49px | 83px |
Reference
iPhone X: Dealing with Home Indicator
Positioning Content Relative to the Safe Area
[Human Interface Guidelines] (https://developer.apple.com/ios/human-interface-guidelines/overview/iphone-x/)