當自動布局遇到ScrollView

<p>2012年,Auto Layout作為iOS6版本的一部分,首次在iOS中露面。發展至今,Auto Layout已經成為了iOS中一種重要的布局技術。然而,每個版本的iOS中,自動布局的實現都會有小小的改進,這種改進,或者說是差異,對于開發者來說既是好消息,又是壞消息。一方面這個技術在不斷的變得好用和更加完善,另一方面對于需要兼容到老版本的開發者來說,這又意味著許多額外的坑需要去踩。</p>
<p> 有很多文章介紹了該如何使用autolayout,并且針對原生Autolayout不好用的接口,也有不少第三方的封裝。然而,當在自動布局中應用scrollview的時候,總有些讓人不爽的地方。明明在iOS8上跑的好好的界面,放到iOS7上就可能有各種問題,不是顯示不全,就是突然滑不動了。如果還要兼容iOS6的系統,簡直就直接想換成frame布局算了,好在用iOS6的人確實不多了。
</p>
<p>關于這個問題,有篇博文(<a >UIScrollview與Autolayout的那點事</a>)講的很清楚,它給的<a >例子</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 >UIScrollview與Autolayout的那點事</a>”很好的介紹了單個UIScrollView如何確定它的contentSize,仔細閱讀它的<a >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的高度。
效果如圖:


123.png

這個例子是將上面提到的文章中的第一個例子做了改造而來,具體加入了對付多個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>

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容