Controller的View布局及滾動(dòng)視圖內(nèi)容自動(dòng)偏移相關(guān)問(wèn)題的探討

從事iOS開發(fā)的小伙伴應(yīng)該都知道這么一個(gè)東西,滾動(dòng)視圖的內(nèi)容部分為了避免被 UINavigationBar 和 UITabBar 遮擋,蘋果官方對(duì)滾動(dòng)視圖內(nèi)容區(qū)域繪制做了處理,會(huì)讓滾動(dòng)視圖內(nèi)容區(qū)域繪制做一定偏移。

image.png

如上圖,紅色 tableView 的 cell 會(huì)自動(dòng)從導(dǎo)航欄下方進(jìn)行布局。

在 iOS11 發(fā)布之后,對(duì)這個(gè)機(jī)制還精心了修改。如果不進(jìn)行一波細(xì)致的梳理,就會(huì)對(duì)這個(gè)問(wèn)題比較頭疼。本文就探討一下這個(gè)問(wèn)題,為iOS開發(fā)新手閉坑。

這里面其實(shí)有兩個(gè)問(wèn)題需要搞清楚。(本文拿導(dǎo)航欄舉例子,UITabBar 同理)

  • Controller的View布局(View的位置在哪里)
  • 滾動(dòng)視圖內(nèi)容自動(dòng)偏移

Controller的View布局

這個(gè)問(wèn)題的必要性,大家都明白吧。首先要找對(duì) view 的位置啊,不然你子視圖的布局 frame 怎么寫,你那個(gè) y值 到底寫 0 還是寫 導(dǎo)航欄的高 呢?

影響View布局的因素有一下幾個(gè):

  1. 不存在導(dǎo)航欄

不存在導(dǎo)航欄的情況,就沒(méi)什么好說(shuō)的了,view的frame(0,0,屏幕寬,屏幕高)

image.png
  1. 存在導(dǎo)航欄

    • 導(dǎo)航欄透明 (navigationBar.isTranslucent = true)
      存在導(dǎo)航欄時(shí),當(dāng)導(dǎo)航欄為透明的時(shí)候,view的frame(0,0,屏幕寬,屏幕高)和無(wú)導(dǎo)航欄時(shí)一樣
image.png
  • 導(dǎo)航欄非透明 (navigationBar.isTranslucent = false)
    • 導(dǎo)航欄非透明時(shí),默認(rèn)情況下,view的frame(0,導(dǎo)航欄高+狀態(tài)欄高,屏幕寬,屏幕高-(導(dǎo)航欄高+狀態(tài)欄高))


      image.png

這里有個(gè)注意點(diǎn),在 viewDidLoad 的方法中,獲取 view 的 frame 是不準(zhǔn)確的,永遠(yuǎn)都是 (0,0,屏幕寬,屏幕高)。 因此要避免在 viewDidLoad 中讀取 view 的 frame 做相關(guān)布局的處理。

另外,UIViewController 還有兩個(gè)屬性可以控制 view 的布局。

// 設(shè)置 extendedLayoutIncludesOpaqueBars 為 true 時(shí),無(wú)論導(dǎo)航欄是否透明,view 都會(huì)從(0,0)開始布局。
self.extendedLayoutIncludesOpaqueBars = true

// 設(shè)置 edgesForExtendedLayout 為 none 時(shí),無(wú)論導(dǎo)航欄是否透明,view 都會(huì)從(0,導(dǎo)航欄高+狀態(tài)欄高)開始布局。
self.edgesForExtendedLayout = []

// 兩個(gè)屬性同時(shí)設(shè)置時(shí),以 edgesForExtendedLayout 為準(zhǔn)。

通過(guò)上面的描述,大家應(yīng)該可以搞清楚,controller 的 view 的布局是怎樣了,接下來(lái)就要研究 滾動(dòng)視圖內(nèi)容自動(dòng)偏移 的問(wèn)題了。

滾動(dòng)視圖內(nèi)容自動(dòng)偏移

探討這個(gè)問(wèn)題就要分成兩部分來(lái)研究了。

  1. iOS11 之前
    大家都知道通過(guò)設(shè)置 controller 的 automaticallyAdjustsScrollViewInsets = true 來(lái)實(shí)現(xiàn)自動(dòng)偏移。仔細(xì)研究就會(huì)發(fā)現(xiàn)這個(gè)東西的注意點(diǎn)(坑...)太多了。
    視圖的自動(dòng)偏移機(jī)制只對(duì)添加到controller.view上的第一個(gè)視圖進(jìn)行處理,若該視圖為scrollview及其子視圖,則對(duì)齊內(nèi)容區(qū)域的布局進(jìn)行一定偏移。
    否則,將會(huì)判斷該視圖的第一個(gè)子視圖是否為scrollview及其子視圖,若該視圖為scrollview及其子視圖,則對(duì)齊內(nèi)容區(qū)域的布局進(jìn)行一定偏移。
    就這樣逐層遍歷

    如下圖,若在view上先添加一個(gè)視圖的話,tableView 的內(nèi)容就會(huì)被遮擋。
    image.png

    再看下圖,當(dāng)view上添加兩個(gè)tableview的時(shí)候,只有第一個(gè)視圖會(huì)進(jìn)行偏移。
    image.png

    下圖,view上放了兩個(gè)子view,子view上添加兩個(gè)滾動(dòng)視圖,可以看到只有第一個(gè)子視圖上的滾動(dòng)視圖進(jìn)行了偏移。
    image.png

    滾動(dòng)視圖是否被導(dǎo)航欄遮擋,都會(huì)進(jìn)行偏移。
    image.png

通過(guò)上面的描述,大家應(yīng)該能夠明白什么情況下會(huì)進(jìn)行自動(dòng)偏移了吧。正是由于里面有這么多細(xì)節(jié),而大家在實(shí)際開發(fā)中可能會(huì)忽略,就導(dǎo)致布局上可能會(huì)產(chǎn)生問(wèn)題,會(huì)讓我們覺(jué)得這個(gè)自動(dòng)偏移并不好用。
可能是蘋果開發(fā)也發(fā)現(xiàn)了上面的問(wèn)題,因此在iOS11之后對(duì)齊廢棄,提供了新的解決方案。

  1. iOS11 及之后
    在iOS11之前,滾動(dòng)視圖是否偏移是通過(guò)controller進(jìn)行控制的,這個(gè)設(shè)計(jì)就感覺(jué)怪怪的。因此在iOS11之后的改為了通過(guò)設(shè)置 scrollview 的 contentInsetAdjustmentBehavior 屬性來(lái)進(jìn)行控制,這樣就科學(xué)多了。(另外 iOS11 還引入了安全區(qū)域的概念,這個(gè)大家執(zhí)行去理解)
    該屬性有一下四個(gè)可選值
    • UIScrollViewContentInsetAdjustmentNever
      這個(gè)就是不進(jìn)行調(diào)整,內(nèi)容區(qū)域從(0,0)開始。


      image.png
    • UIScrollViewContentInsetAdjustmentAlways
      存在導(dǎo)航欄時(shí),偏移到剛好不被導(dǎo)航欄遮擋,如果沒(méi)有被導(dǎo)航欄遮擋就不會(huì)進(jìn)行偏移。這一點(diǎn)就比iOS11之前高級(jí)了N個(gè)層級(jí)(之前是固定偏移一個(gè)值)


      image.png

      不存在導(dǎo)航欄時(shí),會(huì)避開安全區(qū)域。


      image.png
    • UIScrollViewContentInsetAdjustmentScrollableAxes
      官方對(duì)齊解釋如下:
      // Edges for scrollable axes are adjusted (i.e., contentSize.width/height > frame.size.width/height or alwaysBounceHorizontal/Vertical = YES)
      在我理解看來(lái),只有滾動(dòng)視圖可以滾動(dòng)的情況下才會(huì)進(jìn)行調(diào)整。但實(shí)驗(yàn)結(jié)果是,只要 isScrollEnabled 為true,就會(huì)進(jìn)行調(diào)整,避免被導(dǎo)航欄遮擋。
    • UIScrollViewContentInsetAdjustmentAutomatic
      這個(gè)是默認(rèn)值,為了兼容處理。
      如果scrollview在一個(gè)automaticallyAdjustsScrollViewInsets = YES的controller上,并且這個(gè)Controller包含在一個(gè)navigation controller中,無(wú)論能否滾動(dòng)都會(huì)避免被遮擋。其他情況下與UIScrollViewContentInsetAdjustmentScrollableAxes相同。

經(jīng)過(guò)上面這一堆圖文,大家如果看明白了,就不會(huì)再為視圖的偏移產(chǎn)生疑惑了。
在實(shí)際開發(fā)中,我這邊偏向于把自動(dòng)偏移關(guān)掉,自己來(lái)控制視圖的布局。不然每次布局的時(shí)候都得小心翼翼考慮一堆因素,還得注意對(duì)不同系統(tǒng)的兼容。

我覺(jué)得最簡(jiǎn)單的方法就是通過(guò)下面的代碼來(lái)處理。


// 通過(guò)這個(gè)代碼來(lái)保證 view 別被遮擋(會(huì)避開導(dǎo)航欄,但是安全區(qū)域?qū)ζ錄](méi)有影響)。
// 存在導(dǎo)航欄(0,導(dǎo)航欄+狀態(tài)欄),不存在導(dǎo)航欄(0,0)。
self.edgesForExtendedLayout = []

// 關(guān)掉自動(dòng)偏移的設(shè)置,因?yàn)?view 不可能被遮擋,您就別偏移了。
if #available(iOS 11.0, *){
    tableView.contentInsetAdjustmentBehavior = UIScrollView.ContentInsetAdjustmentBehavior.never
}else{
    automaticallyAdjustsScrollViewInsets = false
}

//滾動(dòng)視圖貼著view的四邊就OK了,對(duì)安全區(qū)域的適配通過(guò) tableHeaderView 和 tableFooterView 來(lái)處理。
tableView.snp_makeConstraints { (make) in
    make.edges.equalToSuperview()
}

希望本文對(duì)大家有幫助。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容