前言
自從寫了介紹Masonry那篇文章以後 就一直有人對UIScrollView的那個例子不是很理解
UIView *container = [UIView new]; [scrollView addSubview:container]; [container mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(scrollView); make.width.equalTo(scrollView); }];
為什麼要用一個container包含其他subview?
為什麼指定了edges 還要指定width? 不是多此一舉嗎?
那麼今天我就按照我的理解來說明一下這個問題
梳理
直入主題 要解釋之前的問題 最重要的一個概念就是
UIScrollView依靠與其subviews之間的約束來確定ContentSize的大小
換成代碼 是這個樣子
[scrollView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(v1.mas_left); make.right.equalTo(v1.mas_right); make.top.equalTo(v1.mas_top); make.bottom.equalTo(v1.mas_bottom); }];
這是因為UIScrollView是個非常特殊的view UIScrollView與其subview之間相對位置的約束 並不會直接用於frame的計算 而是會轉化為對ContentSize的計算
換句話說 當UIScrollView知道了上下左右的約束分別指向subview什麼位置之後 只要subview的位置固定下來了 ContentSize的大小就確定下來了
下面來個簡單的例子 強烈建議配合demo來理解下面的例子(demo的鏈接在文尾)
請點擊->在線演示 (為了方便理解 我將ContentSize用紅線框了出來 另外為了查看ContentSize 我把UIScrollView的clipTobounds關閉了 可以通過左上角的開關來切換實際的效果)
示例1
[v1 mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(scrollView); make.width.equalTo(scrollView); make.height.equalTo(scrollView).multipliedBy(1.5); }];
效果
這裡我建立了一個寬等於scrollview 高等於scrollview高度1.5倍的view 然後scrollview成功的計算出了ContentSize
關鍵就在於
make.edges.equalTo(scrollView);
這句話其實等同與之前我提到的
[scrollView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(v1.mas_left); make.right.equalTo(v1.mas_right); make.top.equalTo(v1.mas_top); make.bottom.equalTo(v1.mas_bottom); }];
scrollview因為上面的約束 會以v1的大小來計算ContentSize
示例2
如果嘗試改變v1的大小 會怎麼樣呢?
[v1 mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(scrollView); make.size.equalTo(scrollView).sizeOffset(CGSizeMake(80, 80)); }];
效果
能看到 當我僅改變v1的大小 而不變其他的東西的情況下 scrollview的ContentSize也是隨著v1的大小變化而變化的
示例3
接下來示例就會稍微復雜點 如果同時有兩個view 會如何呢?
[v1 mas_makeConstraints:^(MASConstraintMaker *make) { make.left.top.right.equalTo(scrollView).insets(UIEdgeInsetsMake(10, 10, 0, 10)); make.width.equalTo(scrollView).multipliedBy(1.1); make.bottom.equalTo(v2.mas_top).offset(-50); make.height.equalTo(@200); }]; [v2 mas_makeConstraints:^(MASConstraintMaker *make) { make.bottom.equalTo(scrollView); make.left.right.equalTo(v1).insets(UIEdgeInsetsMake(0, 50, 0, 50)); make.height.equalTo(@250); }];
效果
這個例子中 scrollview的四個方向的約束並沒有放在同一個subview上 而是分別指向了兩個view 所以scrollview的ContentSize會根據兩個view之間的約束來確定
示例4
如果將四個方向的約束分別放到四個不同的view上面 會怎麼樣呢?
CGSize size = CGSizeMake(200, 200); [v1 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(scrollView.mas_top); make.size.mas_equalTo(size); }]; [v2 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(scrollView.mas_left); make.size.mas_equalTo(size); make.right.equalTo(v1.mas_left); make.top.equalTo(v1.mas_bottom); }]; [v3 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(scrollView.mas_right); make.size.mas_equalTo(size); make.left.equalTo(v1.mas_right); make.top.equalTo(v1.mas_bottom); }]; [v4 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(scrollView.mas_bottom); make.size.mas_equalTo(size); make.left.equalTo(v1.mas_left); make.top.equalTo(v2.mas_bottom); }];
效果
將四個方向的約束分別指向四個view的中心點 我們也能得到正確的ContentSize
如果你看懂了示例4的代碼與效果 相信你對這個問題的所有疑惑都應該已經解除了
那麼再回到最開始那個問題
make.edges.equalTo(scrollView); make.width.equalTo(scrollView);
一般情況下我們使用UIScrollView來進行autolayout布局 都是為了實現類似Android中的線性布局(有很多雜的非重復性的subview 如果使用UITableView和UICollectionView太麻煩) 這時直接使用UIScrollView就會很靈活
那麼
如果我們需要豎向的滑動 就把width設為和scrollview相同
如果需要橫向的滑動 就把height設為和scrollview相同
就是這麼簡單
小結
源碼和Demo請點這裡
前不久@nixzhu也寫了一篇關於UIScrollView的文章 然後我在微博上回復說“使用一個單一的containerView占滿全部,然後把所有的subview添加到containerView中” 不過nixzh 表示他是極力避免這樣的 但是後在這個問題上 我是極力推薦這樣使用的
就如同示例1和示例2一樣 如果你需要添加subview 你只要簡單的添加到v1上 並添加與v1的約束 就可以獲得正確的ContentSize了
如果不這樣做 就類似示例3和示例4 這些邊界約束都需要一個一個的設置 這其實是沒有必要的
使用單一的containerView其實是這個問題上的最佳實踐