写在前面
本文关键字:Top/Buttom Layout Guide、automaticallyAdjustsScrollViewInsets
我在Storyboard布局TableView的时候,发现TableView和NavigationBar、TabBar在一起的时候,偶尔会出现下移64点,或者够不到底的情况。
为了杜绝这种“偶尔现象”的发生,我在网上搜了一圈,问题是解决了,但是知识点比较散,所以准备自己再理一遍。
如果阅读此文的小伙伴,目前对Frame和Bounds之间关系还比较凌乱的话,可以读一下我的另一篇记录。
一、名词解释与演示
我先介绍一下这次的两位主角吧:
(一)Top Layout Guide:
Indicates the highest vertical extent for your onscreen content, for use with Auto Layout constraints.
--topLayoutGuide
简而言之,就是屏幕中会遮挡你(onscreen content)的障碍物的高度。
下面我们看几种常见情形下的TopLayoutGuide的赋值情况:
情景一:
你的VC是root VC,不被包含在其它VC中
这种情况,当我们在Storyboard设置约束的时候,系统会自动将我们的预览视图向下移动20点,因为系统这时候很确信,TopLayoutGuide = 20.0。
下面我们看一下实际运行的时候,ViewController是如何获得这个数值的。
从上图可以看出,VC会去从Parent处获得Insets,并将自身的topLayoutGuide属性设置为相应的数值。
这里要注意的是,函数调用发生在viewDidLoad之后,viewWillLayoutSubvies之前。
这也是必然的,在没有加入到View Hierarchy之前,你怎么知道自己的Parent是谁。
情景二:
你的VC被包含在其它VC(UINavigation等)中:
这种情况其实没差,只不过就是NavigationController成为根视图,先从Parent请求Insets:
然后我们的VC再从Navigation处获得:
小结
TopLayoutGuide有几点需要注意:
- 是VC属性
- 其数值是由Parent VC定义
- 最早在viewWillLayoutSubView时决定,在此之前数值皆为0(官方建议我们在viewDidLayoutSubViews中调用)
BottomLayoutGuide原理基本相同,就不重复了。
(二)AutomaticallyAdjustsScrollViewInsets
A Boolean value that indicates whether the view controller should automatically adjust its scroll view insets.
-- AutomaticallyAdjustsScrollViewInsets
一句话,是否由VC调整自身的ScrollView的ContentInset(对ContentInset迷糊的小伙伴看这里)。
这个属性是iOS7之后新增的,默认是开启状态。
情景一
我们设定TableView 与Super View完全契合,填充一些数据之后,其余均为Default:
可以看到,AutomaticallyAdjustsScrollViewInsets并不会自适应StatusBar。
情景二
保持情景一的约束不变,背景色为蓝色,我们将VC嵌入NavigationController:
此时,TableView的Frame并未发生变化,变化的只是TableView.subviews的位置。
我们来打印一下TableView:
MJRefreshCustom[43902:10185773] tableView.contentSize:{320, 880}
MJRefreshCustom[43902:10185773] tableView.contentInset:{64, 0, 0, 0}
MJRefreshCustom[43902:10185773] tableView.contentOffset:{0, -64}
可以看到,VC已经将TableView的ContentInset、ContentOffset(bounds.origin)的值修改了。
ContentSize与ContentInset本质上是决定了TableView.bounds.origin在四个方向上的变化范围,而且origin总是会修改自己的初始值为最小值,可以这么理解:
0 - contentInset.left <= origin.x <= (contentSize.width - bound.size.width) - contentInset.right
0 - contentInset.top <= origin.x <= (contentSize.height - bound.size.height) - contentInset.bottom
所以,如果我们关闭弹簧效果,即
self.table.bounces = NO;
并保持ContentInset的默认值。
参考上面的公式,我们的TableView的orgin此时只能在Y轴上变化:
0 <= origin.x <= 0
0 <= origin.y <= contentSize.height - bounds.size.height
当VC修改TableView的ContentInset.top属性时,其实是变相增大了origin的在Y轴上的变化范围。
-64 <= origin.y <= contentSize.height - bounds.size.height
也就是说,修改TableView.contentInset.top的值为64,实际上是修改ContentOffset(bounds.origin)的值为-64。
回顾一下subView.actualPoint的计算公式:
subView.actualX = subView.frame.origin.x - superView.bounds.origin.x;
subView.actualY = subView.frame.origin.y - superView.bounds.origin.Y;
这样当subView.frame.origin不变时,便会把所有添加到TableView中的SubViews(比如cells)的真实坐标点下移64。
情景三
让我想起了套娃……
TableView配置依旧不变,嵌入Navigation再嵌入TabBar:
小结
从上面的例子可以看出,在我们正确地配置UITableView的约束(请注意,正确地配置)的情况下,开启AutomaticallyAdjustsScrollViewInsets都能很好的处理ScrollView的嵌套问题。
其中有几点需要注意:
- 是VC属性
- 系统默认开启
- 在只有StatusBar的情况下无效
- 必须正确配置UITableView的约束,否则会坑你(详情见下文)
二、设置TableView约束的正确姿势
情景一:
不嵌套
在TableView不嵌入其它VC中的时候,只有StatusBar遮挡,AutomaticallyAdjustsScrollViewInsets对StatusBar无效,所以我们直接使用TopLayoutGuide好了。
情景二:
嵌入Navigation
看似没啥问题:
认真看了上面介绍部分的内容的童鞋,肯定一眼就看出来问题出在哪里了。
首先,TopLayoutGuide.length = 64,这没什么争议,而且TableView也如我们设定般的展示了,但是为啥中间多了一块蓝色的东西?别忘了,蓝色是我设定的TableView的背景色,让我们看一下视图切面:
看出来了吧,这是我们的TableView被暴露出来了。
其实这是AutomaticallyAdjustsScrollViewInsets的坑:
我们禁用它之后,显示就正常了。
这是因为,AutomaticallyAdjustsScrollViewInsets只会关心SubViews里有没有UIScrollView,如果有,它就调整ScrollView.contentInset = {64,0,0,0},这一调整,就把ScrollView的SubViews的位置下移了64点,background就被暴露出来了。
换句话说,它根本不会考虑你设置了怎样的约束。
情景三
再嵌套
方法一:
这里我们直接沿用情景二的约束并禁止AutomaticallyAdjustsScrollViewInsets就可以了。
方法二:
这个方法就是介绍AutomaticallyAdjustsScrollViewInsets时候用的,只需要把TableView的约束设定为与SuperView契合即可。
小结
方法一与方法二是两种实现方式:
一种是修改TableView的约束,使其正好位于NavigationBar与TabBar中间;
一种是修改TableView的ContentInset,使TableView的SubViews产生偏移。
两种方式,各有各的特点,各位按需使用吧。
总结
AutomaticallyAdjustsScrollViewInsets与Top/Buttom Layout Guide在某种程度上,是冲突的,因为两者的实现方式不一样,所以,在开发中,我们应该根据自己的需要正确部署约束:
- 如果TableView的约束与Top/Buttom Layout Guide挂钩,那么就应该关闭AutomaticallyAdjustsScrollViewInsets
- 否则,使用AutomaticallyAdjustsScrollViewInsets会是更好的选择。
容我这里多说一句,之所以出现这种问题,我相信很多童鞋在设置约束的时候,都是想直接约束到SuperView的上部/底部的,结果很多时候,系统默认会给我们约束到Top/Bottom LayoutGuide上,而系统又会默认开启AutomaticallyAdjustsScrollViewInsets属性,这两个默认碰到一起,就会出现跟我们预想不一样的结果。