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

如上圖,紅色 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è):
- 不存在導(dǎo)航欄
不存在導(dǎo)航欄的情況,就沒(méi)什么好說(shuō)的了,view的frame(0,0,屏幕寬,屏幕高)

-
存在導(dǎo)航欄
- 導(dǎo)航欄透明 (navigationBar.isTranslucent = true)
存在導(dǎo)航欄時(shí),當(dāng)導(dǎo)航欄為透明的時(shí)候,view的frame(0,0,屏幕寬,屏幕高)和無(wú)導(dǎo)航欄時(shí)一樣
- 導(dǎo)航欄透明 (navigationBar.isTranslucent = true)

- 導(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)研究了。
- 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ì)齊廢棄,提供了新的解決方案。
- 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ì)大家有幫助。







