UITableView  Layout 上的是是非非

来源:互联网 时间:2017-01-22


写在前面

本文关键字: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)的值修改了。



我们先确定一下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

我们禁用它之后,显示就正常了。
这是因为,AutomaticallyAdjustsScrollViewInsets只会关心自己的View是不是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属性,这两个默认碰到一起,就会出现跟我们预想不一样的结果。




相关阅读:
Top