Auto Layout中Stack View的使用
這是關於自動佈局的第二篇文章。
ofollow,noindex"><< Auto Layout的使用上一篇 文章介紹瞭如何使用Auto Layout,這一篇文章主要介紹 堆疊檢視(Stack View) 。 Stack View 提供了一種輕鬆的方式來使用Auto Layout,不需要引入複雜的約束。單個堆疊檢視定義使用者介面的行或列,堆疊檢視根據以下屬性來排列其子檢視。
- axis :定義 Stack View 的方向,水平方向或豎直方向,只適用於
UIStackView
。NSStackView
中定義方向使用orientation
屬性。 - distribution :設定檢視沿軸線的排布方式。
- alignment :設定如何沿軸線垂直方向排布子檢視。
- spacing :設定子檢視間距。

UIStackViewProperties.png
UIStackView
適用於iOS 9.0+和tvOS 9.0+, NSStackView
適用於macOS 10.9+。這篇文章只講解 UIStackView
。
使用 Stack View 時可以先從物件庫中拖拽出 Horizontal Stack View 或 Vertical Stack View 到 storyboard ,後把需要新增的檢視放進 Stack View ;也可以先新增檢視,後點擊Auto Layout工具 Embed in Stack ,Auto Layout會根據檢視佈局插入水平或垂直堆疊檢視,也可以點選 Editor > Embed In > Stack View 插入堆疊檢視,與點選 Embed In Stack 插入沒有區別。
1. 建立demo
這裡使用 Tabbed Application 的模板建立demo, Product Name 為 StackView ,選擇檔案位置,建立工程。
下面通過三個示例來學習 Stack View 。
2. 示例1
開啟剛建立demo的 Main.storyboard ,在 First Scene 新增以下檢視:第一行為 UILabel
和 UISwitch
,第二行為兩個 UIImageView
。

StackViewD1Storyboard.png
選中第一行的 UILabel
和 UISwitch
,點選 Embed In Stack 插入 Stack View 。開啟 Attributes Inspector ,設定 Stack View 的屬性如下:
- Axis:Horizontal,自動建立的應該為 Horizontal ,不需要修改。
- Alignment:Center
- Distribution:Fill,不需要修改。
- Spacing:
16
選中兩個 UIImageView
,重複上面插入 Stack View 步驟並修改屬性,屬性與上面相同。 UIImageView
中圖片的 Content Mode 屬性為 Aspect Fit 。
選中剛新增的兩個 Stack View ,點選 Embed In Stack 再次插入一個堆疊檢視。堆疊檢視可以嵌入堆疊檢視。介面構建器會自動插入一個垂直堆疊檢視。修改其屬性如下:
堆疊檢視根據子檢視大小來調整自身大小,這裡子檢視保持固有內容大小,所以只需要新增約束指定堆疊檢視位置,不需要約束堆疊檢視大小。
新增約束指定堆疊檢視水平居中,與 Top 距離為 20
points。如果新增過程遇到問題,你可以點選 這裡 檢視demo,也可以檢視上一篇文章學習如何 新增約束 。新增完畢後檢視層級如下:

StackViewD1Hierarchy.png
使用 Stack View 時一般只需要指定堆疊檢視位置,堆疊檢視大小會根據子檢視大小動態調整。
堆疊檢視中子檢視顯示順序由其在 arrangedSubviews
陣列的順序決定。在水平堆疊檢視中,檢視顯示方向與閱讀方向一致, arrangedSubviews
陣列中低索引號檢視先顯示。在垂直堆疊檢視中,檢視從上向下顯示,低索引號檢視在上,高索引號檢視在下。
當向 arrangedSubviews
陣列中新增、移除檢視時,或檢視被隱藏時, Stack View 會自動調整佈局。
下面從 UISwitch
連接出一個 IBAction 的屬性,當點選 UISwitch
時,調整 imageStackView
的 axis
。 imageStackView
為 storyboard 中圖片堆疊檢視的 IBOutlet 屬性。
- (IBAction)axisChange:(UISwitch *)sender { [UIView animateWithDuration:0.25 animations:^{ [self updateConstraintsForAxis]; }]; } - (void)updateConstraintsForAxis { // 在水平、垂直堆疊檢視間切換 if (self.imageStackView.axis == UILayoutConstraintAxisHorizontal) { self.imageStackView.axis = UILayoutConstraintAxisVertical; } else { self.imageStackView.axis = UILayoutConstraintAxisHorizontal; } }
使用 animateWithDuration: animations:
方法可以讓檢視的變換以動畫形式呈現。

StackViewD1AxisChange.gif
3. 示例2
通過 示例1
我們可以在 runtime 手動修改堆疊檢視 axis
,但更好的方式是 Stack View 自動跟隨裝置旋轉。例如,當裝置從 豎屏(portrait) 旋轉為 橫屏(landscape) 時,堆疊檢視 axis
屬性自動從 UILayoutConstraintAxisVertical
調整為 UILayoutConstraintAxisHorizontal
。
進入 Second Scene ,新增兩個 UIImageView
,其 contentMode
屬性為 Aspect Fit ,圖片分別為 Heart 、 Star ,可以通過文章底部網址下載原始碼獲取圖片。選中兩個 UIImageView
插入一個 Horizontal 堆疊檢視。堆疊檢視與 Leading 、 Trailing 、 Top 、 Bottom 距離分別為 0
、 0
、 Standard Value
、 Standard Value
。 Stack View 其它屬性如下:

StackViewD2Stroyboard.png
上面的 Distribution 有 Fill 、 Fill Equally 、 Fill Proportionally 、 Equal Spacing 、 Equal Centering 五個屬性,這五個屬性的區別:
- UIStackViewDistributionFill : Stack View 調整子檢視大小以便填充所有可用空間。當子檢視大小大於可用空間時,根據 Compression Resistance 優先順序壓縮檢視;當子檢視不能填充滿可用空間時,根據 Content Hugging 優先順序拉伸檢視。如果優先順序相同,優先調整
arrangedSubviews
陣列中 index 小的檢視。 - UIStackViewDistributionFillEqually :調整所有子檢視為相同大小,佔用所有可用空間。
- UIStackViewDistributionFillProportionally :會保持每一個子檢視的固有大小,但如果有可用空間、或需要壓縮檢視,會按比例拉伸、壓縮。如一個檢視寬為
100
,另一個檢視寬為200
, Stack View 想要拉伸檢視以便填充可用空間,第一個檢視寬被拉伸為150
,第二個檢視寬就會被拉伸為300
。 - UIStackViewDistributionEqualSpacing :不調整子檢視大小,通過移動子檢視位置讓子檢視間距相等。如果子檢視大於可用空間,會按照 Compression Resistance 優先順序壓縮。如果優先順序相同,優先調整
arrangedSubviews
陣列中 index 小的檢視。 - UIStackViewDistributionEqualCentering :讓子檢視的中心距離相等。如果檢視大於可用空間,會壓縮 spacing 屬性直到設定的最小值,如果此時仍舊大於可用空間,會根據子檢視 Compression Resistance 優先順序壓縮子檢視。如果優先順序相同,優先調整
arrangedSubviews
陣列中 index 小的檢視。
選中 Stack View ,開啟 Attributes Inspector ,你會發現每一個 Stack View 屬性前會有一個 +
。

StackViewD2Attributes.png
點選這些 +
,可以自定義水平、垂直堆疊檢視的屬性。下面新增一個當寬度為 compact 、高度為 regular 時 axis
為垂直的屬性。

StackViewD2AttributesAxis.png
你還可以通過相同方式,新增一個當寬度為 compact 、高度為 regular 時 spacing
屬性,一般不需要新增其它屬性。
現在執行app,當豎屏時,堆疊檢視的 axis 是垂直的;當橫屏時,堆疊檢視的 axis 是水平的。

StackViewAxis.gif
4. 示例3
Stack View 主要優點之一是會自動為其每個子檢視建立自動佈局約束,也可以對這些子檢視的大小和位置進行設定。
為剛建立的demo新增一個 View Controller ,並連線到選項卡控制器,設定 Bar Item 的標題為 Third
。
開啟 Third Scene ,自上而下新增 UILabel
、 UIImageView
、 UIButton
,並插入到一個垂直堆疊檢視中。堆疊檢視屬性設定如下:

StackViewD3Stroyboard.png
其中 UIImageView
的 contentMode
為 Aspect Fit ,為上面 Stack View 新增約束,與 Leading 、 Trailing 、 Top 距離分別為 0
、 0
、 20
。
在上圖 Add Star 下面新增一個水平堆疊檢視,屬性設定如下:
完成後為其新增約束,與 Leading 、 Trailing 、 Top 、 Bottom 距離分別為 0
、 0
、 Standard Value
、 20
,高度為 120
。

StackViewD3StoryboardFinal.png
雖然 UIStackView
繼承自 UIView
,但它只管理子檢視的位置和大小,不提供使用者介面,也就是有些屬性不適用於 UIStackView
,如 backgroundColor
,也不能重寫 drawRect:
方法。
堆疊檢視中有 subviews
和 arrangedSubviews
兩個屬性,其遵守以下規則。
- 當 Stack View 向
arrangedSubviews
陣列新增檢視時,也會將該檢視新增為subviews
。 - 從 Stack View 移除一個檢視,該檢視也會被從
arrangedSubviews
陣列移除。 - 從
arrangedSubviews
陣列移除一個檢視,該檢視依然存在於subviews
陣列。 Stack View 不在管理被移除檢視的位置和大小,但它會存在於檢視層級中。
所以,通過呼叫 addArrangedSubview:
或 insertArrangedSubview: atIndex:
方法向 arrangedSubviews
陣列新增元素,該元素同時會被加入 subviews
陣列。通過 removeFromSuperView
方法移除的檢視,會被同步從 arrangedSubviews
陣列移除。通過 removeArrangedSubview:
移除的檢視,還會存在於 subviews
陣列。
從 UIButton
連接出一個名為 addStar
的 IBAction 點選事件。當點選時,在底部的 horizontalStackView
新增一個五角星。
- (IBAction)addStar:(UIButton *)sender { UIImageView *filledStarView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"filledStar"]]; // 新增檢視到堆疊檢視 [self.horizontalStackView addArrangedSubview:filledStarView]; filledStarView.contentMode = UIViewContentModeScaleAspectFit; [UIView animateWithDuration:0.25 animations:^{ [self.horizontalStackView layoutIfNeeded]; }]; }
現在增加移除五角星的功能。從物件庫拖拽一個 UIButton
到 Add Star 下方。選中 UIButton
和 Add Star ,點選 Embed In Stack 按鈕插入堆疊檢視。修改剛插入堆疊檢視屬性如下:
剛新增 UIButton
標題為 Remove Star
,文字顏色為 red
。

StackViewD3Remove.png
為剛新增的 UIButton
建立名為 removeStar
的IBAction點選事件,並實現移除方法。
- (IBAction)removeStar:(UIButton *)sender { UIView *filledView = self.horizontalStackView.arrangedSubviews.lastObject; // 如果檢視存在,移除檢視 if (filledView) { [filledView removeFromSuperview];// 會自動從arrangedSubviews移除 [UIView animateWithDuration:0.25 animations:^{ [self.horizontalStackView layoutIfNeeded]; }]; } }
執行app,現在可以新增、移除五角星,且檢視會隨著裝置旋轉自動調整佈局。

StackViewD3.gif
總結
通過這篇文章,可以看到 UIStackView
極大降低了使用者介面開發難度,簡化了許多工作,僅僅新增少量約束便可實現自動佈局。在佈局時,應該優先考慮使用 Stack View 來佈局介面。
Demo名稱:StackView
原始碼地址: https://github.com/pro648/BasicDemos-iOS
參考資料: