| 導(dǎo)語(yǔ) 本文主要是對(duì)iOS 11下企鵝 FM APP中tableView內(nèi)容下移20pt或下移64pt的問(wèn)題適配的一個(gè)總結(jié)。內(nèi)容包括五個(gè)部分:?jiǎn)栴}的原因分析、adjustContentInset屬性的計(jì)算方式缠借、什么情況下的tableView會(huì)發(fā)生內(nèi)容下移、有哪些解決方法信不、解決這個(gè)問(wèn)題時(shí)遇到的另外一個(gè)小問(wèn)題屈溉。
一、iOS 11下APP中tableView內(nèi)容下移20pt或下移64pt的原因分析
問(wèn)題如下圖所示:
- 原因分析
原因是iOS 11中Controller的automaticallyAdjustsScrollViewInsets屬性被廢棄了姐扮,所以當(dāng)tableView超出安全區(qū)域時(shí)系統(tǒng)自動(dòng)調(diào)整了SafeAreaInsets值絮供,進(jìn)而影響adjustedContentInset值,在iOS 11中決定tableView的內(nèi)容與邊緣距離的是adjustedContentInset屬性茶敏,而不是contentInset壤靶。adjustedContentInset的計(jì)算方式見(jiàn)本文第二部分內(nèi)容。因?yàn)橄到y(tǒng)對(duì)adjustedContentInset值進(jìn)行了調(diào)整惊搏,所以導(dǎo)致tableView的內(nèi)容到邊緣的距離發(fā)生了變化贮乳,導(dǎo)致tableView下移了20pt(statusbar高度)或64pt(navigationbar高度)。
如果你的APP中使用的是自定義的navigationbar恬惯,隱藏掉系統(tǒng)的navigationbar向拆,并且tableView的frame為(0,0,SCREENWIDTH, SCREENHEIGHT)開(kāi)始,那么系統(tǒng)會(huì)自動(dòng)調(diào)整SafeAreaInsets值為(20,0,0,0)酪耳,如果使用了系統(tǒng)的navigationbar浓恳,那么SafeAreaInsets值為(64,0,0,0),如果也使用了系統(tǒng)的tabbar碗暗,那么SafeAreaInsets值為(64,0,49,0)颈将。關(guān)于什么情況下會(huì)發(fā)生內(nèi)容下移的問(wèn)題,本文第三部分有介紹讹堤。 -
安全區(qū)域的概念
系統(tǒng)自動(dòng)調(diào)整tableView內(nèi)容偏移量吆鹤,是根據(jù)安全區(qū)域來(lái)調(diào)整的。安全區(qū)域是iOS 11新提出的洲守,如下圖所示:
安全區(qū)域幫助我們將view放置在整個(gè)屏幕的可視的部分疑务。即使把navigationbar設(shè)置為透明的沾凄,系統(tǒng)也認(rèn)為安全區(qū)域是從navigationbar的bottom開(kāi)始,保證不被系統(tǒng)的狀態(tài)欄知允、或?qū)Ш綑诟采w撒蟀。可以使用additionalSafeAreaInsets去擴(kuò)展安全區(qū)域使它包括自定義的content在界面上温鸽。每個(gè)view都可以改變安全區(qū)域嵌入的大小保屯,Controller也可以。
safeAreaInsets屬性反映了一個(gè)view距離該view的安全區(qū)域的邊距涤垫。對(duì)于一個(gè)Controller的根視圖而言姑尺,SafeAreaInsets值包括了被statusbar和其他可視的bars覆蓋的區(qū)域和其他通過(guò)additionalSafeAreaInsets自定義的insets值。view層次中的其它view蝠猬,SafeAreaInsets值反映了該view被覆蓋的部分切蟋。如果一個(gè)view全部在它父視圖的安全區(qū)域內(nèi),則SafeAreaInsets值為(0,0,0,0)榆芦。
二柄粹、 adjustContentInset屬性的計(jì)算方式
首先看scrollView在iOS11新增的兩個(gè)屬性:adjustContentInset 和 contentInsetAdjustmentBehavior。
/* Configure the behavior of adjustedContentInset.Default is UIScrollViewContentInsetAdjustmentAutomatic.*/@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior
adjustContentInset表示contentView.frame.origin偏移了scrollview.frame.origin多少匆绣;是系統(tǒng)計(jì)算得來(lái)的驻右,計(jì)算方式由contentInsetAdjustmentBehavior決定。有以下幾種計(jì)算方式:
1.UIScrollViewContentInsetAdjustmentAutomatic:如果scrollview在一個(gè)automaticallyAdjustsScrollViewContentInset = YES的controller上崎淳,并且這個(gè)Controller包含在一個(gè)navigation controller中堪夭,這種情況下會(huì)設(shè)置在top & bottom上 adjustedContentInset = safeAreaInset + contentInset不管是否滾動(dòng)。其他情況下與UIScrollViewContentInsetAdjustmentScrollableAxes相同
2.UIScrollViewContentInsetAdjustmentScrollableAxes: 在可滾動(dòng)方向上adjustedContentInset = safeAreaInset + contentInset凯力,在不可滾動(dòng)方向上adjustedContentInset = contentInset茵瘾;依賴于scrollEnabled和alwaysBounceHorizontal / vertical = YES礼华,scrollEnabled默認(rèn)為yes咐鹤,所以大多數(shù)情況下,計(jì)算方式還是adjustedContentInset = safeAreaInset + contentInset
**3.UIScrollViewContentInsetAdjustmentNever: **adjustedContentInset = contentInset
4.UIScrollViewContentInsetAdjustmentAlways: adjustedContentInset = safeAreaInset + contentInset
當(dāng)contentInsetAdjustmentBehavior設(shè)置為UIScrollViewContentInsetAdjustmentNever的時(shí)候圣絮,adjustContentInset值不受SafeAreaInset值的影響祈惶。
三、什么情況下的tableView會(huì)發(fā)生上述問(wèn)題
如果設(shè)置了automaticallyAdjustsScrollViewInsets = YES扮匠,那么不會(huì)發(fā)生問(wèn)題捧请,一直都是由系統(tǒng)來(lái)調(diào)整內(nèi)容的偏移量。
接下來(lái)排查下自己的項(xiàng)目中哪些頁(yè)面會(huì)發(fā)生以上問(wèn)題棒搜。
當(dāng)tableView的frame超出安全區(qū)域范圍時(shí)疹蛉,系統(tǒng)會(huì)自動(dòng)調(diào)整內(nèi)容的位置,SafeAreaInsets值會(huì)不為0力麸,于是影響tableView的adjustContentInset值可款,于是影響tableView的內(nèi)容展示育韩,導(dǎo)致tableView的content下移了SafeAreaInsets的距離。SafeAreaInsets值為0時(shí)闺鲸,是正常的情況筋讨。
需要了解每個(gè)頁(yè)面的結(jié)構(gòu),看tableView是否被系統(tǒng)的statusbar或navigationbar覆蓋摸恍,如果被覆蓋的話悉罕,則會(huì)發(fā)生下移。也可以通過(guò)tableview.safeAreaInsets的值來(lái)確認(rèn)是因?yàn)榘踩珔^(qū)域的問(wèn)題導(dǎo)致的內(nèi)容下移立镶。
如下代碼片段壁袄,可以看出系統(tǒng)對(duì)tableView向下調(diào)整了20pt的距離,因?yàn)閠ableView超出了安全區(qū)域范圍媚媒,被statusbar覆蓋然想。
tableview.contentInset: {64, 0, 60, 0}tableview.safeAreaInsets: {20, 0, 0, 0}tableview.adjustedContentInset: {84, 0, 60, 0}
四、這個(gè)問(wèn)題的解決方法有哪些欣范?
- 重新設(shè)置tableView的contentInset值变泄,來(lái)抵消掉SafeAreaInset值,因?yàn)閮?nèi)容偏移量 = contentInset + SafeAreaInset恼琼;
如果之前自己設(shè)置了contentInset值為(64,0,0,0),現(xiàn)在系統(tǒng)又設(shè)置了SafeAreaInsets值為(64,0,0,0)妨蛹,那么tableView內(nèi)容下移了64pt,這種情況下晴竞,可以設(shè)置contentInset值為(0,0,0,0)蛙卤,也就是遵從系統(tǒng)的設(shè)置了。 - 設(shè)置tableView的contentInsetAdjustmentBehavior屬性
如果不需要系統(tǒng)為你設(shè)置邊緣距離噩死,可以做以下設(shè)置:
//如果iOS的系統(tǒng)是11.0颤难,會(huì)有這樣一個(gè)宏定義“#define __IPHONE_11_0 110000”;如果系統(tǒng)版本低于11.0則沒(méi)有這個(gè)宏定義#ifdef __IPHONE_11_0 if ([tableView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;}#endif
contentInsetAdjustmentBehavior屬性也是用來(lái)取代automaticallyAdjustsScrollViewInsets屬性的已维,推薦使用這種方式行嗤。
-
通過(guò)設(shè)置iOS 11新增的屬性addtionalSafeAreaInset;
iOS 11之前垛耳,大家是通過(guò)將Controller的automaticallyAdjustsScrollViewInsets屬性設(shè)置為NO栅屏,來(lái)禁止系統(tǒng)對(duì)tableView調(diào)整contentInsets的。如果還是想從Controller級(jí)別解決問(wèn)題堂鲜,那么可以通過(guò)設(shè)置Controller的additionalSafeAreaInsets屬性栈雳,如果SafeAreaInset值為(20,0,0,0),那么設(shè)置additionalSafeAreaInsets屬性值為(-20,0,0,0)缔莲,則SafeAreaInsets不會(huì)對(duì)adjustedContentInset值產(chǎn)生影響哥纫,tableView內(nèi)容不會(huì)顯示異常。這里需要注意的是addtionalSafeAreaInset是Controller的屬性痴奏,要知道SafeAreaInset的值是由哪個(gè)Controller引起的蛀骇,可能是由自己的Controller調(diào)整的奖慌,可能是navigationController調(diào)整的。是由哪個(gè)Controller調(diào)整的松靡,則設(shè)置哪個(gè)Controller的addtionalSafeAreaInset值來(lái)抵消掉SafeAreaInset值简僧。
五、遇到的另外一個(gè)與安全區(qū)域無(wú)關(guān)的tableView內(nèi)容下移的問(wèn)題
我的作品頁(yè)面的tableView下移了約40pt雕欺,這里是否跟安全區(qū)域有關(guān)呢岛马?
查了下頁(yè)面結(jié)構(gòu),tableView的父視圖的frame在navigationbar的bottom之下屠列,tableView在父視圖的安全區(qū)域內(nèi)啦逆,打印出來(lái)tableView的SafeAreaInset值也是(0,0笛洛,0夏志,0);所以不是安全區(qū)域?qū)е碌膬?nèi)容下移。
經(jīng)過(guò)查看代碼苛让,發(fā)現(xiàn)tableView的style:UITableViewStyleGrouped類型沟蔑,默認(rèn)tableView開(kāi)頭和結(jié)尾是有間距的,不需要這個(gè)間距的話狱杰,可以通過(guò)實(shí)現(xiàn)heightForHeaderInSection方法(返回一個(gè)較小值:0.1)和viewForHeaderInSection(返回一個(gè)view)來(lái)去除頭部的留白瘦材,底部同理。
iOS 11上發(fā)生tableView頂部有留白仿畸,原因是代碼中只實(shí)現(xiàn)了heightForHeaderInSection方法食棕,而沒(méi)有實(shí)現(xiàn)viewForHeaderInSection方法。那樣寫是不規(guī)范的错沽,只實(shí)現(xiàn)高度簿晓,而沒(méi)有實(shí)現(xiàn)view,但代碼這樣寫在iOS 11之前是沒(méi)有問(wèn)題的千埃,iOS 11之后應(yīng)該是由于開(kāi)啟了估算行高機(jī)制引起了bug憔儿。添加上viewForHeaderInSection方法后,問(wèn)題就解決了镰禾∶笄或者添加以下代碼關(guān)閉估算行高,問(wèn)題也得到解決吴侦。
self.tableView.estimatedRowHeight = 0;self.tableView.estimatedSectionHeaderHeight = 0;self.tableView.estimatedSectionFooterHeight = 0;