适配iOS11和iPhone X的经验总结
感觉好久没有静下心来总结和记录技术问题了,博客文章也好久没有更新了。。。虽然平时工作上也都会不断遇到新的问题和难点,但基于繁忙的业务开发和自己最近的一些私事缠身,遇到问题基本都是快速解决就草草了事了,没有深入的总结和记录,回想真是于“颓废”了。今天着手总结一下前一阵子适配iOS11和iPhone X遇到的一些问题和经验总结。
一、iOS11引发的问题
1、在iOS11下tableview内容下移20pt或者60pt,iOS11之前页面没有问题。(下图tableview的frame设置为super view的frame,隐藏了系统导航栏。问题:tableview的内容下移了20pt ?)
如上图所示,从iOS11开始,系统增加了一个safe area概念。我们先来介绍safe area这个东西是用来做什么的。safe area即安全区域,安全区域帮助我们将view的内容放置在整个屏幕的可视的部分。它定义了view中可视内容的区域,保证不被系统的状态栏、导航栏等其他控件覆盖。即使把navigationbar设置为透明的,系统也认为安全区域是从navigationbar的bottom开始的。所以上图的安全区域是从状态栏的bottom开始的。
区别:IOS11之后控制tableview的内容和边缘距离的属性是adjustedContentInset,而在iOS11之前tableview是根据contentInset来确定内容与边缘的距离的。
我们先回顾iOS11之前的contentInset。contentInset是用来描述tableview内容的上下左右与边缘的距离的,它可以确定tableview显示内容的区域。iOS系统从iOS7开始使用视图边缘延伸特性,即全屏布局。这种特性下,就算你的视图里面有navigationbar和tabbar,view的frame仍然会是 (0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),这样的结果就是navigationbar和tabbar会把view的顶部和底部区域覆盖住。所以controller在iOS7的时候新增了一个属性 automaticallyAdjustsScrollViewInsets,默认是YES。这个属性就是控制滚动视图内容和边缘距离的,即默认修改contentInset的值保证滚动视图的内容不会被导航栏、状态栏挡住。如果有navigationbar,那么contentInset就是(64, 0, 0 , 0),如果还有tabbar,那么contentInset就是(64, 0, 0 , 49)。
但是在iOS11之后,属性automaticallyAdjustsScrollViewInsets开始被废弃了,contentInset默认是(0, 0, 0 , 0)。增加了safe area概念。而体现safe area的是safeAreaInset属性,它定义了view中可视内容的区域,保证不被系统的状态栏、导航栏等其他控件覆盖。如上图safeAreaInset是(20, 0, 0 , 0)。iOS11之后决定tableview内容和边缘距离的属性是adjustedContentInset。adjustedContentInset的值计算方式由contentInsetAdjustmentBehavior决定,默认情况下adjustedContentInset = contentInset + safeAreaInset 。
1. UIScrollViewContentInsetAdjustmentAutomatic: 如果scrollview在一个automaticallyAdjustsScrollViewInsets = YES的controller上,并且这个Controller包含在一个navigation controller中,这种情况下会设置在top & bottom上 adjustedContentInset = safeAreaInset + contentInset不管是否滚动。其他情况下与UIScrollViewContentInsetAdjustmentScrollableAxes相同
2. UIScrollViewContentInsetAdjustmentScrollableAxes: 在可滚动方向上adjustedContentInset = safeAreaInset + contentInset,在不可滚动方向上adjustedContentInset = contentInset;依赖于scrollEnabled和alwaysBounceHorizontal / vertical = YES,scrollEnabled默认为yes,所以大多数情况下,计算方式还是adjustedContentInset = safeAreaInset + contentInset
3. UIScrollViewContentInsetAdjustmentNever: adjustedContentInset = contentInset
4. UIScrollViewContentInsetAdjustmentAlways: adjustedContentInset = safeAreaInset + contentInset
当contentInsetAdjustmentBehavior设置为UIScrollViewContentInsetAdjustmentNever的时候,adjustContentInset值不受SafeAreaInset值的影响。
那么什么情况下会出现上述问题?
当tableView的frame超出安全区域范围时,系统会自动调整内容的位置保证内容在安全区域内,这时SafeAreaInsets值不为0,于是影响tableView的adjustContentInset值,进而影响tableView的内容展示,导致tableView的content下移了SafeAreaInsets的距离。正常情况下SafeAreaInsets的值为0。
如何解决上述问题?
如果tableview内容下移20pt或者60pt,说明tableView的frame超出了安全区域范围,这个时候safeAreaInset的值不为0。
第一个方法:我们可以通过设置contentInset的值来巧妙抵消safeAreaInset的值,使adjustedContentInset的值为0。即 0 = safeAreaInset + contentInset。
第二个方法:设置contentInsetAdjustmentBehavior=UIScrollViewContentInsetAdjustmentNever,这个时候 adjustedContentInset = contentInset,这样tableView的内容调整就和安全区域无关了,只由contentInset控制。
2、在iOS11下默认开启Self-Sizing
我们都知道tableview的加载机制是这样的:当调用reloadData时,tableview会先计算出contentSize的大小,因为要先确定contentView的大小和位置才能决定内容的滚动区域。所以会先调用方法heightForRowAtIndexPath: 。假设tableview有50行,那么这个方法会调用50次,计算出contentSize的大小,然后才会调用cellForRowAtIndexPath:去创建cell。
在iOS8引入Self-Sizing 之后,我们可以通过实现estimatedRowHeight相关的属性来展示动态的内容,不用一次性把所有的行高都计算出来。实现了estimatedRowHeight属性后,得到的初始contenSize是个估算值,是通过estimatedRowHeight 乘以 cell的个数得到的,并不是最终的contenSize,tableView就不会一次性计算所有的cell的高度了,只会计算当前屏幕能够显示的cell个数再加上几个。然后在滑动的时候,tableView不停地创建新的cell,不断的更新自己的contenSize,在滑到最后的时候,就会得到正确的contenSize。这样的好处就是更高效了,滑动更顺滑了。
如果以前没有使用estimateRowHeight属性,那么在iOS11下就要注意一个问题:因为开启Self-Sizing之后,tableView是使用estimateRowHeight属性的,这样就会造成contentSize和contentOffset值的变化,如果是有动画是观察这两个属性的变化进行的,就会造成动画的异常,因为在估算行高机制下,contentSize的值是一点点地变化更新的,所有cell显示完后才是最终的contentSize值。而且tableview不会缓存正确的行高,所以reloadData的时候,会重新计算contentSize。
iOS11下不想使用Self-Sizing的话,可以通过以下方式关闭:
self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;
二、适配iPhone X
1、APP界面上的变化
2、设计和技术实现上的变化
界面设计上,大方向的标注和控件布局依旧按照原先iPhone 8及以前的机型进行设计:
3、开发处理iPhone X相关代码处理
上一篇: 使用沉浸式全屏模式