<p>2012年,Auto Layout作为iOS6版本的一部分,首次在iOS中露面。发展至今,Auto Layout已经成为了iOS中一种重要的布局技术。然而,每个版本的iOS中,自动布局的实现都会有小小的改进,这种改进,或者说是差异,对于开发者来说既是好消息,又是坏消息。一方面这个技术在不断的变得好用和更加完善,另一方面对于需要兼容到老版本的开发者来说,这又意味着许多额外的坑需要去踩。</p>
<p> 有很多文章介绍了该如何使用autolayout,并且针对原生Autolayout不好用的接口,也有不少第三方的封装。然而,当在自动布局中应用scrollview的时候,总有些让人不爽的地方。明明在iOS8上跑的好好的界面,放到iOS7上就可能有各种问题,不是显示不全,就是突然滑不动了。如果还要兼容iOS6的系统,简直就直接想换成frame布局算了,好在用iOS6的人确实不多了。
</p>
<p>关于这个问题,有篇博文(<a href="http://adad184.com/2015/12/01/scrollview-under-autolayout/">UIScrollview与Autolayout的那点事</a>)讲的很清楚,它给的<a href="https://github.com/adad184/DemoScrollViewAutolayout">例子</a>也恰到好处的说明了问题的本质:
自动布局中,UIScrollView 依靠与其subviews之间的约束来确定ContentSize的大小!
上面这个概念很重要,理解了上面那句话,我们才能在自动布局中应用好UIScrollView;如何来理解呢?UIScrollView是个非常特殊的view,UIScrollView与其subview之间相对位置的约束 并不会直接用于frame的计算,而是会转化为对ContentSize的计算。换句话说,当UIScrollView知道了上下左右的约束分别指向subview什么位置之后,只要subview的位置固定下来了 ContentSize的大小就确定下来了。
<br />
第一段代码:
<code>
[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.eadges.equalTo(v1);
}];
</code>
上面代码只是利用masonry来确定了scrollView的frame,它等同于其子view的v1,然而这并不够,我们知道UIScrollView除了需要指定frame之外,还需要指定ContentSize。
<br />
通过添加于scrollView的子view来确定ContentSize:
第二段代码:
<code>
[subView1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(scrollView);
make.width.equalTo(scrollView);
make.height.equalTo(scrollView).multipliedBy(1.5);}
];
</code>
初学自动布局的人可能以为上面的代码这对subView1的约束有点多余,既然指定了eadges,为何还要指定width和height?岂不是会有约束冲突?
然而这里并没有约束冲突。指定了eadges只是确定了subView1的边界将是scrollView内容页边界(这样理解可能会更清楚些,虽然代码写的是scrollView,但实际效果确实是scrollView的内容页),而并非scrollView自己的Frame。那么再指定width和height,同样确定的是scrollView内容页的宽和高。
对于上面的代码,如果我们去掉关于width和height的约束,则scrollView还可以展示出来,原因是第一段代码中我们指定来scrollview相对v1的约束,即确定了scrollView的显示窗口。不过contentSize却没法计算,会为(0,0)。因为我们指定了subView1相对于contentView的约束,而subView1并未给出具体的宽和高。但是contentView的宽和高需要根据其子View来计算,如果subView1是ScrollView的唯一子View的话,那么具体ContentSize是无法计算的,因为subView1并未提供。
“<a href="http://adad184.com/2015/12/01/scrollview-under-autolayout/">UIScrollview与Autolayout的那点事</a>”很好的介绍了单个UIScrollView如何确定它的contentSize,仔细阅读它的<a href="https://github.com/adad184/DemoScrollViewAutolayout">demo</a>会有更多的收获。
<br />
然而,我们的应用还会有很多更复杂的场景,通常会有多个UIScrollView在同一个界面一起使用的情况,其中横向的scrollView可以横向翻页,而对于每一页,又有纵向的scrollView(通常是UITableView)可以纵向翻页。
场景虽然复杂了些,然而问题的本质还是没有变化,scrollView的contentSize将由其子View来进行计算!
下面的代码中给出了一个主scrollView嵌套另一个子scrollView的情况。子scrollView又可以带上tabview等多个子视图。这是一种常见的布局场景。
<code>
CGFloat width = [[UIScreen mainScreen] bounds].size.width;
CGFloat height = [[UIScreen mainScreen] bounds].size.height;
UIScrollView *scrollView = [UIScrollView new];
scrollView.backgroundColor = [UIColor lightGrayColor];
scrollView.clipsToBounds = NO;
scrollView.bounces = YES;
scrollView.delegate = self;
scrollView.tag = 1;
self.scrollView = scrollView;
[self.view addSubview:scrollView];
[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
UIView* headView = [[UIView alloc] init];
[scrollView addSubview:headView];
[headView setBackgroundColor:[UIColor redColor]];
UIView* tabView = [UIView new];
[scrollView addSubview:tabView];
[tabView setBackgroundColor:[UIColor yellowColor]];
//subviews
UIScrollView* subScrollView = [UIScrollView new];
[scrollView addSubview:subScrollView];
subScrollView.bounces = NO;
subScrollView.clipsToBounds = NO;
subScrollView.pagingEnabled = YES;
subScrollView.tag = 2;
self.subScrollView = subScrollView;
[headView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.equalTo(scrollView);
make.width.mas_equalTo(width);
make.height.mas_equalTo(108);
}];
[tabView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(44);
make.top.equalTo(headView.mas_bottom);
make.left.right.equalTo(headView);
}];
CGFloat subHeight = height-44-49;
[subScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(tabView.mas_bottom);
make.left.right.equalTo(tabView);
make.height.mas_equalTo(subHeight);
make.bottom.equalTo(scrollView).offset(-49);
}];
UIView* greenView = [UIView new];
[subScrollView addSubview:greenView];
greenView.backgroundColor = [UIColor greenColor];
UITableView* tableView= [[UITableView alloc] init];
[subScrollView addSubview:tableView];
tableView.delegate = self;
tableView.dataSource = self;
tableView.tag = 3;
self.tabTable = tableView;
[tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.equalTo(subScrollView);
make.width.mas_equalTo(width);
make.height.mas_equalTo(subHeight);
}];
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(tableView.mas_right);
make.width.height.top.equalTo(tableView);
make.right.bottom.equalTo(subScrollView);
}];
</code>
在确定subScrollView的高度时,使用确定的数字能让结果比较确定,上述代码中使用的是
CGFloat subHeight = height-44-49;
44是tabView的高度,而49是底下tabView的高度。
效果如图:
这个例子是将上面提到的文章中的第一个例子做了改造而来,具体加入了对付多个UIScrollView的情况,至此,如果你彻底明白了上面的例子,那么自动布局应用在UIScrollView也就没有什么问题了。
当多个UIScrollView在一起的时候,滑动时哪个UIScrollView优先响应手势事件就需要一种机制来指定。通过scrollDidScroll方法可以确定具体是谁该滑动。
<code>
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if(_scrollView.contentOffset.y + _scrollView.frame.size.height < _scrollView.contentSize.height) {
_tabTable.scrollEnabled = NO;
}
else {
_tabTable.scrollEnabled = YES;
}
}
</code>
上述逻辑指定主scrollView先滑动,当滑倒底之后,才让table里面的cell可滑。
最后,即便如此,在布局完毕之后,如果你的应用需要支持iOS7,那还是要用对应的系统多跑一跑,没有问题才好。有问题的话,看下对应scrollView的contentSize是否正确,各个scrollView的子View是否可以确定对应的contentView(可以理解为隐藏属性)了。
<br />
在复杂的view带有scrollView的布局中,如果要支持iOS7,最好还是用纯frame布局吧,至少在这种情况下,兼容问题不用再花费额外的时间去处理。
</p>