理論分析
首先,我們知道Autolayout改變了傳統的以frame為主的佈局思想。它其實是一種相對佈局,核心思想是檢視與檢視之間的位置關係。比如,我們可以根據矩形的起始橫座標、縱座標、長和寬這四個變數確定它的位置。或者,如果已經確定矩形A的位置,只要知道矩形B每條邊的和A對應邊之間的距離,也能確定B的位置。前者就是frame的思想,它基於絕對數值,而後者是Autolayout的思想,它基於偏移量的概念。
其次,UIScrollView
有自己的frame也就是我們在螢幕上能看到的區域。它還有一個contentSize
的概念。在使用frame佈局的時候,我們一般先設定好子檢視的位置,最後再設定contentSize
,它會將所有的子檢視包含在內。於是通過滑動,我們就可以在有限的佈局中,看到所有的內容了。
但是在Autolayout時代,為了簡化佈局,我們希望contentSize
能夠自動設定。比如有一個scrollView,它有兩個子檢視。frame分別為(x: 0, y: 0, width: 10, height: 10)和(x: 10, y: 0, width: 10, height: 10),那麼我們自然會認為這兩個檢視左右並排排列,contentSize
為(x: 0, y: 0, width: 20, height: 10):

這種把若干個子檢視合併,得出contentSize
的能力,人類是天生具備的,但是計算機卻不是這樣。僅憑以上資訊,程式無法推斷出真正的contentSize
。原因在於,我們沒有明確的告訴系統,在這兩個子檢視拼接而成的區域以外,還有沒有區域應該被contentSize
包含。
也就是說,contentSize
也有可能是下圖中的陰影部分:

如果需要指定contentSize
就是兩個正方形拼接而成的區域,我們還需要提供四個資訊:
- 左邊的正方形的左側的邊,距離contentSize左邊的距離為0
- 右邊的正方形的右側的邊,距離contentSize右邊的距離為0
……
通過以上的分析,我們可以看到,其實contentSize
是依賴於子檢視自身的大小,和上下左右四個方向的留白大小計算出的。而UIScrollView
的leading/trailing/top/bottom是相對於它的contentSize
而不是bounds
來確定的。所以如果你寫這樣的程式碼,佈局是肯定不會生效的:
subview.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(scrollView).offset(5)
}
因為我們其實是在根據UIScrollView
的leading/trailing/top/bottom來確定子檢視的位置,而我們已經分析過,UIScrollView
的leading/trailing/top/bottom是相對於自己的contentSize
而言的。而contentSize
又是根據子檢視位置決定的。這就變成了一種你依賴我,我又依賴你的情況。
為了打破這種迴圈依賴,為子檢視新增約束的兩個要求是:
- 它不依賴於任何與scrollview有關佈局,也就是不能參考scrollview的位置和大小。
- 它不僅要確定過自己的大小,還要確定自己與contentSize四周的距離。
第二個要求意思是說,正常使用autolayout時,我們確定一個矩形在水平方向上的範圍,只要知道它的左邊距離它左邊的矩形有多遠,以及它有多寬即可。但是在UIScrollView
中佈局時,還需要告訴UIScrollView
,它的右邊距離右邊的檢視有多遠。這樣contentSize
才能確定。否則UIScrollView
就不知道contentSize
向右可以延伸多少。在豎直方向上也是同理。
這兩大要求一定要牢記!接下來我們的程式碼都將圍繞如何滿足這兩大要求展開。
動手實踐
明白了問題的理論背景後,我們通過一個具體的需求,來看看正確的程式碼怎麼寫,以下面這個效果為例:

如圖所示,中間是一個UIScrollView
,它的背景顏色是黃色。紅色部分我們稱之為box
,它是一個普通的,紅色背景的UIView
。也就是說我們向UIScrollView
中添加了多個box
,每個子box
之間間隔一定距離。我們分步實現這個功能
使用container
首先我們介紹一種使用Container的方法。
第一步:為scrollView新增約束
let scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.snp_makeConstraints { (make) -> Void in
make.centerY.equalTo(view.snp_centerY)
make.left.right.equalTo(view)
make.height.equalTo(topScrollHeight)
}
我們之前說過,使用Autolayout時,不用考慮frame佈局。所以直接建立一個scrollView
物件。需要先把scrollView
新增到父檢視上才能新增約束。
對scrollView
新增約束沒有什麼難點,就像我們給其他檢視新增約束一樣。這裡表示scrollView
和父檢視左右對齊,居中顯示。
第二步:為container新增約束
scrollView.addSubview(containerView)
containerView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(scrollView)
make.height.equalTo(topScrollHeight)
}
這裡對container
的約束非常重要,第一個約束表示自己上、下、左、右和contentSize
的距離為0,因此只要container
的大小確定,contentSize
也就可以確定了,因為此時它和container
大小、位置完全相同。
第二個約束直接通過一個數值,確定container
的高度。避免了依賴scrollview
佈局。這樣一來,scrollview
就變成水平的了。container
的寬度直接決定了scrollview
的寬度。