iPhone X適配——你需要知道這些

原文出自我在團隊技術公眾號發表的 《隨手記在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 在應用中代表的意義。

日歷應用中不同場景下的 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 取代了 bottomLayoutGuidetopLayoutGuide,對于已經使用了 bottomLayoutGuidetopLayoutGuide 的布局來說,單純使用 safeAreaLayoutGuide 可以完成一樣的工作,同時也可以完美適配 iPhone X 的布局。

使用 Auto Layout 布局,適配 safe area 會是一個非常簡單的事情。打開一個 Storyboard,會發現視圖層次中多了一個 Safe Area

Storyboard中的safe area

所以,使用 Auto Layout 布局的話,safe area 就相對于一個處于每個 view controller 中處于視圖層級底層的容器,我們的子控件只需要和它建立約束關系,就可以完美適配 iPhone X 安全區域

與SafeArea建立約束

約束示例
約束示例效果圖

Programming with Safe Area

如果使用代碼進行布局,對于 safe area 適配也不會復雜。iOS 11 中 UIView 的新 API SafeAreaInsetsUIViewController 的新 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

Designing for iPhone X

iPhone X: Dealing with Home Indicator

SafeAreaLayoutGuide

Positioning Content Relative to the Safe Area

[Human Interface Guidelines] (https://developer.apple.com/ios/human-interface-guidelines/overview/iphone-x/)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容