<p>2012年,Auto Layout作為iOS6版本的一部分,首次在iOS中露面。發(fā)展至今,Auto Layout已經(jīng)成為了iOS中一種重要的布局技術(shù)。然而,每個(gè)版本的iOS中,自動(dòng)布局的實(shí)現(xiàn)都會(huì)有小小的改進(jìn),這種改進(jìn),或者說是差異,對(duì)于開發(fā)者來說既是好消息,又是壞消息。一方面這個(gè)技術(shù)在不斷的變得好用和更加完善,另一方面對(duì)于需要兼容到老版本的開發(fā)者來說,這又意味著許多額外的坑需要去踩。</p>
<p> 有很多文章介紹了該如何使用autolayout,并且針對(duì)原生Autolayout不好用的接口,也有不少第三方的封裝。然而,當(dāng)在自動(dòng)布局中應(yīng)用scrollview的時(shí)候,總有些讓人不爽的地方。明明在iOS8上跑的好好的界面,放到iOS7上就可能有各種問題,不是顯示不全,就是突然滑不動(dòng)了。如果還要兼容iOS6的系統(tǒng),簡(jiǎn)直就直接想換成frame布局算了,好在用iOS6的人確實(shí)不多了。
</p>
<p>關(guān)于這個(gè)問題,有篇博文(<a >UIScrollview與Autolayout的那點(diǎn)事</a>)講的很清楚,它給的<a >例子</a>也恰到好處的說明了問題的本質(zhì):
自動(dòng)布局中,UIScrollView 依靠與其subviews之間的約束來確定ContentSize的大小!
上面這個(gè)概念很重要,理解了上面那句話,我們才能在自動(dòng)布局中應(yīng)用好UIScrollView;如何來理解呢?UIScrollView是個(gè)非常特殊的view,UIScrollView與其subview之間相對(duì)位置的約束 并不會(huì)直接用于frame的計(jì)算,而是會(huì)轉(zhuǎn)化為對(duì)ContentSize的計(jì)算。換句話說,當(dāng)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>
初學(xué)自動(dòng)布局的人可能以為上面的代碼這對(duì)subView1的約束有點(diǎn)多余,既然指定了eadges,為何還要指定width和height?豈不是會(huì)有約束沖突?
然而這里并沒有約束沖突。指定了eadges只是確定了subView1的邊界將是scrollView內(nèi)容頁邊界(這樣理解可能會(huì)更清楚些,雖然代碼寫的是scrollView,但實(shí)際效果確實(shí)是scrollView的內(nèi)容頁),而并非scrollView自己的Frame。那么再指定width和height,同樣確定的是scrollView內(nèi)容頁的寬和高。
對(duì)于上面的代碼,如果我們?nèi)サ絷P(guān)于width和height的約束,則scrollView還可以展示出來,原因是第一段代碼中我們指定來scrollview相對(duì)v1的約束,即確定了scrollView的顯示窗口。不過contentSize卻沒法計(jì)算,會(huì)為(0,0)。因?yàn)槲覀冎付藄ubView1相對(duì)于contentView的約束,而subView1并未給出具體的寬和高。但是contentView的寬和高需要根據(jù)其子View來計(jì)算,如果subView1是ScrollView的唯一子View的話,那么具體ContentSize是無法計(jì)算的,因?yàn)閟ubView1并未提供。
“<a >UIScrollview與Autolayout的那點(diǎn)事</a>”很好的介紹了單個(gè)UIScrollView如何確定它的contentSize,仔細(xì)閱讀它的<a >demo</a>會(huì)有更多的收獲。
<br />
然而,我們的應(yīng)用還會(huì)有很多更復(fù)雜的場(chǎng)景,通常會(huì)有多個(gè)UIScrollView在同一個(gè)界面一起使用的情況,其中橫向的scrollView可以橫向翻頁,而對(duì)于每一頁,又有縱向的scrollView(通常是UITableView)可以縱向翻頁。
場(chǎng)景雖然復(fù)雜了些,然而問題的本質(zhì)還是沒有變化,scrollView的contentSize將由其子View來進(jìn)行計(jì)算!
下面的代碼中給出了一個(gè)主scrollView嵌套另一個(gè)子scrollView的情況。子scrollView又可以帶上tabview等多個(gè)子視圖。這是一種常見的布局場(chǎng)景。
<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的高度時(shí),使用確定的數(shù)字能讓結(jié)果比較確定,上述代碼中使用的是
CGFloat subHeight = height-44-49;
44是tabView的高度,而49是底下tabView的高度。
效果如圖:

這個(gè)例子是將上面提到的文章中的第一個(gè)例子做了改造而來,具體加入了對(duì)付多個(gè)UIScrollView的情況,至此,如果你徹底明白了上面的例子,那么自動(dòng)布局應(yīng)用在UIScrollView也就沒有什么問題了。
當(dāng)多個(gè)UIScrollView在一起的時(shí)候,滑動(dòng)時(shí)哪個(gè)UIScrollView優(yōu)先響應(yīng)手勢(shì)事件就需要一種機(jī)制來指定。通過scrollDidScroll方法可以確定具體是誰該滑動(dòng)。
<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先滑動(dòng),當(dāng)滑倒底之后,才讓table里面的cell可滑。
最后,即便如此,在布局完畢之后,如果你的應(yīng)用需要支持iOS7,那還是要用對(duì)應(yīng)的系統(tǒng)多跑一跑,沒有問題才好。有問題的話,看下對(duì)應(yīng)scrollView的contentSize是否正確,各個(gè)scrollView的子View是否可以確定對(duì)應(yīng)的contentView(可以理解為隱藏屬性)了。
<br />
在復(fù)雜的view帶有scrollView的布局中,如果要支持iOS7,最好還是用純frame布局吧,至少在這種情況下,兼容問題不用再花費(fèi)額外的時(shí)間去處理。
</p>