本文为作者原创,未经作者允许不得转载。该文同时发表在腾讯bugly公众号:http://mp.weixin.qq.com/s/W1_0VrchCO50owhJNmJnuQ 欢迎阅读
导语:本文主要是对iOS 11下APP中tableView
内容下移20pt或下移64pt的问题适配的一个总结。内容包括五个部分:问题的原因分析、adjustContentInset
属性的计算方式、什么情况下的tableView
会发生内容下移、有哪些解决方法、解决这个问题时遇到的另外一个小问题。
一、iOS 11下APP中tableView内容下移20pt或下移64pt的原因分析
问题如下图所示:
![](https://img.haomeiwen.com/i927233/558062c2a3cd2ac9.png)
1. 原因分析
原因是iOS 11中Controller的automaticallyAdjustsScrollViewInsets
属性被废弃了,所以当tableView超出安全区域时系统自动调整了SafeAreaInsets
值,进而影响adjustedContentInset
值,在iOS 11中决定tableView的内容与边缘距离的是adjustedContentInset
属性,而不是contentInset
。adjustedContentInset
的计算方式见本文第二部分内容。因为系统对adjustedContentInset
值进行了调整,所以导致tableView的内容到边缘的距离发生了变化,导致tableView下移了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)。关于什么情况下会发生内容下移的问题,本文第三部分有介绍。
2. 安全区域的概念
系统自动调整tableView内容偏移量,是根据安全区域来调整的。安全区域是iOS 11新提出的,如下图所示:
![](https://img.haomeiwen.com/i927233/a9933365dabc2532.png)
安全区域帮助我们将view放置在整个屏幕的可视的部分。即使把navigationbar设置为透明的,系统也认为安全区域是从navigationbar的bottom开始的。
安全区域定义了view中可视区域的部分,保证不被系统的状态栏、或父视图提供的view如导航栏覆盖。可以使用additionalSafeAreaInsets
去扩展安全区域去包括自定义的content在你的界面。每个view都可以改变安全区域嵌入的大小,Controller也可以。
safeAreaInsets
属性反映了一个view距离该view的安全区域的边距。对于一个Controller的根视图而言,SafeAreaInsets
值包括了被statusbar
和其他可视的bars覆盖的区域和其他通过additionalSafeAreaInsets
自定义的insets值。对于view层次中得其他view,SafeAreaInsets
值反映了view被覆盖的部分。如果一个view全部在它父视图的安全区域内,则SafeAreaInsets
值为(0,0,0,0)。
二、 adjustContentInset属性的计算方式
首先看scrollView在iOS11新增的两个属性:adjustContentInset
和 contentInsetAdjustmentBehavior
。
/* Configure the behavior of adjustedContentInset.
Default is UIScrollViewContentInsetAdjustmentAutomatic.
*/
@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior
adjustContentInset
表示contentView.frame.origin偏移了scrollview.frame.origin多少;是系统计算得来的,计算方式由contentInsetAdjustmentBehavior
决定。有以下几种计算方式:
-
UIScrollViewContentInsetAdjustmentAutomatic
:如果scrollview在一个automaticallyAdjustsScrollViewInsets = YES的controller上,并且这个Controller包含在一个navigation controller中,这种情况下会设置在top & bottom上 adjustedContentInset = safeAreaInset + contentInset不管是否滚动。其他情况下与UIScrollViewContentInsetAdjustmentScrollableAxes
相同 -
UIScrollViewContentInsetAdjustmentScrollableAxes
: 在可滚动方向上adjustedContentInset = safeAreaInset + contentInset,在不可滚动方向上adjustedContentInset = contentInset;依赖于scrollEnabled和alwaysBounceHorizontal / vertical = YES,scrollEnabled默认为yes,所以大多数情况下,计算方式还是adjustedContentInset = safeAreaInset + contentInset -
UIScrollViewContentInsetAdjustmentNever
: adjustedContentInset = contentInset -
UIScrollViewContentInsetAdjustmentAlways
: adjustedContentInset = safeAreaInset + contentInset
当contentInsetAdjustmentBehavior
设置为UIScrollViewContentInsetAdjustmentNever的时候,adjustContentInset值不受SafeAreaInset
值的影响。
三、什么情况下的tableView会发生上述问题
如果设置了automaticallyAdjustsScrollViewInsets = YES,那么不会发生问题,一直都是由系统来调整内容的偏移量。
接下来排查下自己的项目中哪些页面会发生以上问题。
当tableView的frame超出安全区域范围时,系统会自动调整内容的位置,SafeAreaInsets
值会不为0,于是影响tableView的adjustContentInset
值,于是影响tableView的内容展示,导致tableView的content下移了SafeAreaInsets
的距离。SafeAreaInsets
值为0时,是正常的情况。
需要了解每个页面的结构,看tableView是否被系统的statusbar或navigationbar覆盖,如果被覆盖的话,则会发生下移。也可以通过tableview.safeAreaInsets
的值来确认是因为安全区域的问题导致的内容下移。
如下代码片段,可以看出系统对tableView向下调整了20pt的距离,因为tableView超出了安全区域范围,被statusbar覆盖。
tableview.contentInset: {64, 0, 60, 0}
tableview.safeAreaInsets: {20, 0, 0, 0}
tableview.adjustedContentInset: {84, 0, 60, 0}
四、这个问题的解决方法有哪些?
1. 重新设置tableView的contentInset值,来抵消掉SafeAreaInset值,因为内容下移偏移量 = contentInset + SafeAreaInset;
如果之前自己设置了contentInset
值为(64,0,0,0),现在系统又设置了SafeAreaInsets
值为(64,0,0,0),那么tableView内容下移了64pt,这种情况下,可以设置contentInset
值为(0,0,0,0),也就是遵从系统的设置了。
2. 设置tableView的contentInsetAdjustmentBehavior属性
如果不需要系统为你设置边缘距离,可以做以下设置:
//如果iOS的系统是11.0,会有这样一个宏定义“#define __IPHONE_11_0 110000”;如果系统版本低于11.0则没有这个宏定义
#ifdef __IPHONE_11_0
if ([tableView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
#endif
contentInsetAdjustmentBehavior
属性也是用来取代automaticallyAdjustsScrollViewInsets
属性的,推荐使用这种方式。
3. 通过设置iOS 11新增的属性addtionalSafeAreaInset;
iOS 11之前,大家是通过将Controller的automaticallyAdjustsScrollViewInsets
属性设置为NO,来禁止系统对tableView调整contentInsets
的。如果还是想从Controller级别解决问题,那么可以通过设置Controller的additionalSafeAreaInsets
属性,如果SafeAreaInset
值为(20,0,0,0),那么设置additionalSafeAreaInsets
属性值为(-20,0,0,0),则SafeAreaInsets
不会对adjustedContentInset
值产生影响,tableView内容不会显示异常。这里需要注意的是addtionalSafeAreaInset
是Controller的属性,要知道SafeAreaInset
的值是由哪个Controller引起的,可能是由自己的Controller调整的,可能是navigationController调整的。是由哪个Controller调整的,则设置哪个Controller的addtionalSafeAreaInset
值来抵消掉SafeAreaInset
值。
五、遇到的另外一个与安全区域无关的tableView内容下移的问题
我的作品页面的tableView下移了约40pt,这里是否跟安全区域有关呢?
查了下页面结构,tableView的父视图的frame在navigationbar的bottom之下,tableView在父视图的安全区域内,打印出来tableView的SafeAreaInset值也是(0,0,0,0);所以不是安全区域导致的内容下移。
经过查看代码,发现tableView的style:UITableViewStyleGrouped
类型,默认tableView开头和结尾是有间距的,不需要这个间距的话,可以通过实现heightForHeaderInSection方法(返回一个较小值:0.1)和viewForHeaderInSection(返回一个view)来去除头部的留白,底部同理。
iOS 11上发生tableView顶部有留白,原因是代码中只实现了heightForHeaderInSection方法,而没有实现viewForHeaderInSection方法。那样写是不规范的,只实现高度,而没有实现view,但代码这样写在iOS 11之前是没有问题的,iOS 11之后应该是由于开启了估算行高机制引起了bug。添加上viewForHeaderInSection方法后,问题就解决了。或者添加以下代码关闭估算行高,问题也得到解决。
self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;
网友评论
这个 不是废弃了吗?还跟他有关系?
CGFloat sectionHeaderHeight = 10;
if (scrollView.contentOffset.y<=sectionHeaderHeight&&scrollView.contentOffset.y>=0) {
scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
} else if (scrollView.contentOffset.y>=sectionHeaderHeight) {
scrollView.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight, 0, 50, 0);
}
}
博主 我这段代码现在应该怎么改啊
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
self.automaticallyAdjustsScrollViewInsets = NO;
}
和tableView的frame:_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, kNav_Height, KScreenWidth, kScreenHeight) style:UITableViewStyleGrouped];
当我从这个界面push到下一个界面,再pop回来的时候,tableView上面会有一个导航条高度的白条,这是什么原因引起的呢?
这点其实与iOS10当中设置 automaticallyAdjustsScrollViewInsets = YES 改变 scrollView.contentInset 生效条件是相同的:只有scrollView是控制器 view 的第一个子 view 或者 scrollView 是控制器的 view 时, scrollView.contentInset 才会被改变。
从cocoaChina论坛里找到了终极解决方案:
//解决iOS11,仅实现heightForHeaderInSection,没有实现viewForHeaderInSection方法时,section间距大的问题
[UITableView appearance].estimatedRowHeight = 0;
[UITableView appearance].estimatedSectionHeaderHeight = 0;
[UITableView appearance].estimatedSectionFooterHeight = 0;
//iOS11 解决SafeArea的问题,同时能解决pop时上级页面scrollView抖动的问题
if (@available(iOS 11, *)) {
[UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; //iOS11 解决SafeArea的问题,同时能解决pop时上级页面scrollView抖动的问题
}
请把我顶上去,让更多人收益,谢谢!
CGRect rectStatus = [[UIApplication sharedApplication] statusBarFrame];
CGRect rectNav = self.navigationController.navigationBar.frame;
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.top.equalTo(self.view).with.offset(-rectStatus.size.height-rectNav.size.height);
make.left.equalTo(self.view).with.offset(0);
make.right.equalTo(self.view).with.offset(0);
make.bottom.equalTo(self.view).with.offset(0);
}];
我发现我的tableview向下偏移了20pt;
1、首先我打印了:tableview.contentInset
tableview.safeAreaInsets
tableview.adjustedContentInset
都是0。
2.我的tableview是UITableViewStyleGrouped的,我已经实现了viewForHeaderInSection,heightForHeaderInSection,viewForFooterInSection,heightForFooterInSection这些方法;
我按照的你的两种方法都处理了,还是一样呢?怎么找原因啊
if (@available(iOS 11, *))
{
UIView*searchbg=[[UIView alloc]init];
searchbg.backgroundColor=[UIColor clearColor];
[searchbg.heightAnchor constraintEqualToConstant: 44].active = YES;
[searchbg.widthAnchor constraintEqualToConstant:Screen_Width-70].active=YES;
self.navigationItem.titleView=searchbg;
[searchbg addSubview:self.searchbar];
[self.searchbar mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(searchbg);
make.top.equalTo(searchbg).with.offset(7.0);
make.left.equalTo(searchbg).with.offset(0);
make.right.equalTo(searchbg).with.offset(0);
make.bottom.equalTo(searchbg).with.offset(-7.0);
}];
}
else
{
self.navigationItem.titleView=self.searchbar;
}
self.searchbar = [[UISearchBar alloc] init];
self.navigationItem.titleView=self.searchbar;
出现的问题,只要注释掉就没问题了。。。
问题又来了。。怎么修改titleview上面的SearcheView?
#ifdef __IPHONE_11_0
if ([tableView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
#endif
这句话放在哪呢
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
self.tableView.contentInset = UIEdgeInsetsMake(VIEW_5_8_INCH ? 88 : 64, 0, VIEW_5_8_INCH ? 83 : 49, 0);
self.tableView.scrollIndicatorInsets = self.tableView.contentInset;
}
楼主,这个top和bottom还要判断是不是iPhone X,请问还要其它好的解决方案吗?
为什么
设置 tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever恢复正常了,但是跳转到下一个界面,下个界面导航栏出现时有些残影,动画非常不自然,请问有什么解决办法呢?
if ([_tableView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
if (@available(iOS 11.0, *)) {
_tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
#endif
#ifdef __IPHONE_11_0
if (@available(iOS 11.0, *)) {
tv.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
#endif
运行时系统的版本判断有很多种方法。你可以测下Xcode8下跑下代码,Xcode9下跑下iOS 11的设备,iOS 11之前的设备,都看下是否正常,正常的话就是没有问题的
第三大点即是最后一个句话提出,那么我如何判断SafeAreaInset 是 navigationController的属性还是controlller的属性?
系统认为安全区域是从navigationbar的bottom开始的吗?怎么又包含各种bar覆盖的区域了。。。。
tableView.safeAreaInsets: {0, 0, 0, 0}
tableView.contentInsets: {0, 0, 0, 0}
self.navigationController.additionalSafeAreaInsets: {-20, 0, 0, 0}
tableView.adjustedContentInset: {0, 0, 0, 0}
我的例子是只使用了系统的statusbar,改成使用系统的navigationbar,也是一样可以抵消掉的
看代码打印:
(lldb) po NSStringFromUIEdgeInsets(self.additionalSafeAreaInsets)
{-64, 0, -49, 0}
(lldb) po NSStringFromUIEdgeInsets(self.view.safeAreaInsets)
{64, 0, 0, 0}
(lldb) po NSStringFromUIEdgeInsets(_tv.adjustedContentInset)
{64, 0, 0, 0}
(lldb) po NSStringFromUIEdgeInsets(_tv.safeAreaInsets)
{64, 0, 0, 0}
可能理解的不准确,请指正。
[[UIScrollView appearance] setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
}
我直接全部改掉了..