當(dāng)自動(dòng)布局遇到ScrollView

<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的高度。
效果如圖:


123.png

這個(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>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容