UITableView Layout 上的是是非非

写在前面

本文关键字: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中
UITableView Layout 上的是是非非_第1张图片

这种情况,当我们在Storyboard设置约束的时候,系统会自动将我们的预览视图向下移动20点,因为系统这时候很确信,TopLayoutGuide = 20.0。

下面我们看一下实际运行的时候,ViewController是如何获得这个数值的。


从上图可以看出,VC会去从Parent处获得Insets,并将自身的topLayoutGuide属性设置为相应的数值。

这里要注意的是,函数调用发生在viewDidLoad之后,viewWillLayoutSubvies之前。
这也是必然的,在没有加入到View Hierarchy之前,你怎么知道自己的Parent是谁。

情景二:

你的VC被包含在其它VC(UINavigation等)中:
UITableView Layout 上的是是非非_第2张图片

这种情况其实没差,只不过就是NavigationController成为根视图,先从Parent请求Insets:

UITableView Layout 上的是是非非_第3张图片

然后我们的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:


UITableView Layout 上的是是非非_第4张图片
UITableView Layout 上的是是非非_第5张图片
运行结果

可以看到,AutomaticallyAdjustsScrollViewInsets并不会自适应StatusBar。

情景二

保持情景一的约束不变,背景色为蓝色,我们将VC嵌入NavigationController:

UITableView Layout 上的是是非非_第6张图片
UITableView Layout 上的是是非非_第7张图片
运行结果

此时,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)的值修改了。

UITableView Layout 上的是是非非_第8张图片
我们先确定一下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。

情景三

UITableView Layout 上的是是非非_第9张图片

让我想起了套娃……
TableView配置依旧不变,嵌入Navigation再嵌入TabBar:


UITableView Layout 上的是是非非_第10张图片
运行结果

小结

从上面的例子可以看出,在我们正确地配置UITableView的约束(请注意,正确地配置)的情况下,开启AutomaticallyAdjustsScrollViewInsets都能很好的处理ScrollView的嵌套问题。
其中有几点需要注意:

  • 是VC属性
  • 系统默认开启
  • 在只有StatusBar的情况下无效
  • 必须正确配置UITableView的约束,否则会坑你(详情见下文)

二、设置TableView约束的正确姿势

情景一:

不嵌套

UITableView Layout 上的是是非非_第11张图片
UITableView Layout 上的是是非非_第12张图片

在TableView不嵌入其它VC中的时候,只有StatusBar遮挡,AutomaticallyAdjustsScrollViewInsets对StatusBar无效,所以我们直接使用TopLayoutGuide好了。

情景二:

嵌入Navigation

UITableView Layout 上的是是非非_第13张图片

看似没啥问题:


UITableView Layout 上的是是非非_第14张图片

认真看了上面介绍部分的内容的童鞋,肯定一眼就看出来问题出在哪里了。
首先,TopLayoutGuide.length = 64,这没什么争议,而且TableView也如我们设定般的展示了,但是为啥中间多了一块蓝色的东西?别忘了,蓝色是我设定的TableView的背景色,让我们看一下视图切面:

UITableView Layout 上的是是非非_第15张图片

看出来了吧,这是我们的TableView被暴露出来了。
其实这是AutomaticallyAdjustsScrollViewInsets的坑:


UITableView Layout 上的是是非非_第16张图片
禁用AutomaticallyAdjustsScrollViewInsets

我们禁用它之后,显示就正常了。
这是因为,AutomaticallyAdjustsScrollViewInsets只会关心SubViews里有没有UIScrollView,如果有,它就调整ScrollView.contentInset = {64,0,0,0},这一调整,就把ScrollView的SubViews的位置下移了64点,background就被暴露出来了。
换句话说,它根本不会考虑你设置了怎样的约束

情景三

再嵌套

UITableView Layout 上的是是非非_第17张图片

方法一:

这里我们直接沿用情景二的约束并禁止AutomaticallyAdjustsScrollViewInsets就可以了。


UITableView Layout 上的是是非非_第18张图片
运行结果

方法二:

这个方法就是介绍AutomaticallyAdjustsScrollViewInsets时候用的,只需要把TableView的约束设定为与SuperView契合即可。

UITableView Layout 上的是是非非_第19张图片
UITableView Layout 上的是是非非_第20张图片

小结

方法一与方法二是两种实现方式:
一种是修改TableView的约束,使其正好位于NavigationBar与TabBar中间;
一种是修改TableView的ContentInset,使TableView的SubViews产生偏移。
两种方式,各有各的特点,各位按需使用吧。

总结

AutomaticallyAdjustsScrollViewInsets与Top/Buttom Layout Guide在某种程度上,是冲突的,因为两者的实现方式不一样,所以,在开发中,我们应该根据自己的需要正确部署约束:

  • 如果TableView的约束与Top/Buttom Layout Guide挂钩,那么就应该关闭AutomaticallyAdjustsScrollViewInsets
  • 否则,使用AutomaticallyAdjustsScrollViewInsets会是更好的选择。

容我这里多说一句,之所以出现这种问题,我相信很多童鞋在设置约束的时候,都是想直接约束到SuperView的上部/底部的,结果很多时候,系统默认会给我们约束到Top/Bottom LayoutGuide上,而系统又会默认开启AutomaticallyAdjustsScrollViewInsets属性,这两个默认碰到一起,就会出现跟我们预想不一样的结果。

你可能感兴趣的:(UITableView Layout 上的是是非非)