iOS iPhone App

升級iOS 11後關於Safe Area的對應調整

陳傑雄 2017/10/21 15:48:14
6244







主題

升級iOS 11後關於Safe Area的對應調整

文章簡介

APP升級iOS 11後,UITableViewControllerUITableView畫面發生移位問題時,如何從程式碼做相對應的調整。

作者:

陳傑雄

版本/產出日期:

V1.0/2017.09.26




1. 前言

本文主要是對APP升級iOS 11後,UITableViewController或UITableView畫面(或內容)下移(或上移)20pt或64pt的問題調整的一些方式,內容包括五個部份:問題的原因分析、adjustedContentInset屬性的計算、什麼情況下的UITableViewController / UITableView會發生位移、以及有哪些解決方法。

開發工具使用XCode 9.0。

本文件適用於Objective C開發。






2. 目的

了解iOS 11發布後,您該如何調整您的APP。






3. UITableView內容下移或上移20pt / 64pt的原因分析

問題如下圖所示:

圖1. 下移20pt



圖2. 上移20pt


3.1、原因分析

    在Xcode 9新開的專案,預設採用了Safe Area,不再是Top Layout Guide和Bottom Layout Guide了。


    新建立的專案預設會勾選Use Safe Area Layout Guides。(storyboard的File Inspector頁面)

Xcode 9之前的舊專案勾選 Use Safe Area Layout Guides 後,Top Layout Guide Bottom Layout Guide 將被拿掉,變成使用 Safe Area,一般來可以無痛轉換,不會有太大的問題

    


雖然 Safe Area 只支援 iOS 11 以上版本,不過勾選 Use Safe Area Layout Guides 的專案,仍然可以支援舊版本的 iOS,因為 Safe Area 會被自動轉換為 Top and Bottom layout guide。


storyboard 和 xib 都可以個別設定是否使用 Safe Area。但這意味著舊專案中我們設定的Constrain to margins都將被Safe Area自動計算,失去了Top Layout Guide和Bottom Layout Guide與superview之間的基準。

Safe Area能幫助我們將view放置在整個螢幕的可視的部份。即使把navigationbar設置為透明的,系統也認為Safe Area是從navigationbar的bottom開始的。


Safe Area定義了view中可視區域的部份,保證不被系統的statusbar、或parent view提供的view(如navigationbar、tabbar)覆蓋。可以使用additionalSafeAreaInsets擴展Safe Area,讓你在設計的畫面裡可以去包括自訂的content。每個view都可以改變Safe Area嵌入的大小,Controller也可以。


safeAreaInsets屬性表示了一個view距離parent view的Safe Area的邊距。對於一個Controller的root view而言,SafeAreaInsets值包括了被statusbar和其他可視的bars覆蓋的區域和其他通過additionalSafeAreaInsets自訂的insets值。至於view包含於其他view中,SafeAreaInsets值表示了view被覆蓋的部分。 如果一個view全部在它parent view的Safe Area內,則SafeAreaInsets值為(0, 0, 0, 0)。


再來是iOS 11中ViewController的automaticallyAdjustsScrollViewInsets屬性被棄用了(defaultYES),所以當UITableView超出Safe Area時,系統自動調整了SafeAreaInsets的值,進而影響了adjustedContentInsets的值,在iOS 11中決定UITableViews內容與邊緣距離的是adjustedContentInsets屬性,而不是contentInset。


因為iOS 11對adjustedContentInset值做了調整,所以導致UITableView的內容到邊緣的距離發生了變化,導致UITableView下移或上移了20pt(statusbar高度)或64pt(navigationbar高度)。


所以,如果我們的APP中使用了客製的navigationbar,隱藏掉系統的navigationbar,並且tableView的frame是以(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)為起始,那麼系統將會自動調整SafeAreaInsets值為(20, 0, 0 , 0);如果使用了系統的navigationbar,那麼SafeAreaInsets值會變為(64, 0, 0, 0);如果也使用了系統的tabbar,那麼SafeAreaInsets值就會是(64, 0, 49, 0),造成UITableView下移的現象。


而當我們在APP中使用了不含系統的navigationbar、tabbar的UITableViewController, SafeAreaInsets的值會被系統自動調整為(0, 0, 0, 0),造成tableView會與statusbar重疊,形成UITableView上移的現象。






4. adjustContentInset屬性的計算方式

scrollView在iOS 11新增了兩個屬性:adjustContentInset 和 contentInsetAdjustmentBehavior。

依照Apple developer網站的介紹:

/* Configure the behavior of adjustedContentInset.

Default is UIScrollViewContentInsetAdjustmentAutomatic.

*/

@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior

contentInsetAdjustmentBehavior


adjustContentInset表示contentView.frame.origin偏移了scrollview.frame.origin多少,它是系統計算出來的,計算方法由contentInsetAdjustmentBehavior決定。有以下幾種behavior,其計算方式分別如下:

4.1、 UIScrollViewContentInsetAdjustmentAutomatic

如果scrollview是在一個設定automaticallyAdjustsScrollViewInsets=YES的ViewController時,且這個ViewController包含在一個navigation controller中,這種情形下將與UIScrollViewContentInsetAdjustmentScrollableAxes相似,調整top和bottom上的contentInset,adjustedContentInset = safeAreaInset + contentInset,無論scrollView的scrollEnabled是YES或NO。


4.2、 UIScrollViewContentInsetAdjustmentScrollableAxes

在可以捲動的方向上adjustedContentInset = safeAreaInset + contentInset;在不可捲動的方向上,adjustedContentInset = contentInset;依據scrollEnabled和alwaysBounceHorizontal / alwaysBounceVertical=YES,scrollEnables預設是YES,所以大多數的情況下,計算方式還是adjustedContentInset = safeAreaInset + contentInset。


4.3、 UIScrollViewContentInsetAdjustmentNever

adjustedContentInset = contentInset。


4.4、 UIScrollViewContentInsetAdjustmentAlways

adjustedContentInset = safeAreaInset + contentInset。


當contentInsetAdjustmentBehavior設為UIScrollViewContentInsetAdjustmentNever時,adjustContentInset的值不受SafeAreaInset值的影響。






5. 什麼情況下的tableView會發生上述問題?

如果設定了automaticallyAdjustsScrollViewInsets=YES,一切都是由系統來調整內容的偏移量。


接下來,我們來看看發生問題的頁面,如下圖所示,整個tableView向下偏移了20pt,原因在於原始畫面設計是設定了automaticallyAdjustsScrollViewInsets=NO,表示不由系統自動調整內容偏移量,且由程式自己設定contentInset(x=0, y=0),但是在iOS11裡,automaticallyAdjustsScrollViewInsets已經被棄用,而這個屬性預設又是YES,所以當UITableView超出Safe Area時,系統會自動計算SafeAreaInsets,進而影響了adjustedContentInsets,根據前面講的,SafeArea會自動排除navigationbar和statusbar的高度,下圖畫面的設計已經隱藏了navigationbar,但還有statusbar,所以,adjustedContentInsets(20)= safeAreaInsets(20) + contentInset(0),被自動向下移了20pt。


另一個發生問題的頁面,如下圖所示,整個tableView上移了20pt,這個畫面的設計是一個

UITableViewController,它不同於在UIViewController中包UITableView,它沒有Top and Bottom layout guide當基準,UITableView也沒有包在UIView中,即使automaticallyAdjustsScrollViewInsets預設是YES,但它不支援UITableView,所以,整個tableView就貼齊screen的最上面,造成tableView的header和statusbar重疊,畫面上移了20pt。






6. 解決的方法

既然知道了問題原因,那解決方法就因應而生了,第一個下移的問題,(1.) 我們可以透過重新設定tableViewcontentInset,來抵銷掉SafeAreaInset,因為adjustedContentInset=contentInset + SafeAreaInset;(2.) 或是透過設定tableView的contentInsetAdjustmentBehavior屬性來調整,我們可以將它設定為UIScrollViewContentInsetAdjustmentNever來替代原本的self. automaticallyAdjustsScrollViewInsets=NO,因為我們在這個畫面中,不需要系統自動幫我們調整邊距,個人比較偏好後面這個方法,效率最佳。


第二個畫面上移的問題,其發生原因較第一個問題複雜,除了自動調整的問題,還因為UITableViewController這個特殊的控制項,沒有Top and Bottom layout guide當基準,也不支援automaticallyAdjustsScrollViewInsets這個屬性,但是它的解決方法拜iOS 11所賜,可以直接過設定tableView的contentInsetAdjustmentBehavior屬性來調整,所以,直接設定為UIScrollViewContentInsetAdjustmentAlways,讓系統自動計算safeAreaInset,依照前面介紹的,系統會將y軸設為20或64(statusbar高度為20,navigationbar高度為44),原畫面設計隱藏了navigationbar,所以,這個案例會自動計算出safeAreaInset的y=20,問題修正。


另外一些畫面跑版問題,不外乎是原畫面或元件設計時以screen做為基礎來計算,會造成和iOS 11的safeAreaInset的設計不合,這類問題可以透過調整self.view.safeAreaLayoutGuide.layoutFrame的相關屬性做調整。






7. 參考來源

Updating Your App for iOS 11-https://devstreaming-cdn.apple.com/videos/wwdc/2017/204kty9amomlmk222/204/204_updating_your_app_for_ios_11.pdf

陳傑雄