升級iOS 11後關於Safe Area的對應調整
主題: |
升級iOS 11後關於Safe Area的對應調整 |
文章簡介: |
APP升級iOS 11後,UITableViewController或UITableView畫面發生移位問題時,如何從程式碼做相對應的調整。 |
作者: |
陳傑雄 |
版本/產出日期: |
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屬性被棄用了(default是YES),所以當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.) 我們可以透過重新設定tableView的contentInset,來抵銷掉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