這是關于自動布局的第二篇文章。
<< Auto Layout的使用
上一篇文章介紹了如何使用Auto Layout,這一篇文章主要介紹堆棧視圖(Stack View)。Stack View提供了一種輕松的方式來使用Auto Layout,不需要引入復雜的約束。單個堆棧視圖定義用戶界面的行或列,堆棧視圖根據以下屬性來排列其子視圖。
-
axis:定義Stack View的方向,水平方向或豎直方向,只適用于
UIStackView
。NSStackView
中定義方向使用orientation
屬性。 - distribution:設定視圖沿軸線的排布方式。
- alignment:設定如何沿軸線垂直方向排布子視圖。
- spacing:設定子視圖間距。
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
。
選中第一行的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再次插入一個堆棧視圖。堆棧視圖可以嵌入堆棧視圖。界面構建器會自動插入一個垂直堆棧視圖。修改其屬性如下:
- Axis:Vertical,不需要修改。
- Alignment:Center
- Distribution:Fill,不需要修改。
- Spacing:
16
堆棧視圖根據子視圖大小來調整自身大小,這里子視圖保持固有內容大小,所以只需要添加約束指定堆棧視圖位置,不需要約束堆棧視圖大小。
添加約束指定堆棧視圖水平居中,與Top距離為20
points。如果添加過程遇到問題,你可以點擊這里查看demo,也可以查看上一篇文章學習如何添加約束。添加完畢后視圖層級如下:
使用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:
方法可以讓視圖的變換以動畫形式呈現。
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其它屬性如下:
- Axis:Horizontal
- Alignment:Center
- Distribution:Fill Proportionally
- Spacing:
0
上面的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屬性前會有一個+
。
點擊這些+
,可以自定義水平、垂直堆棧視圖的屬性。下面添加一個當寬度為compact、高度為regular時axis
為垂直的屬性。
你還可以通過相同方式,添加一個當寬度為compact、高度為regular時spacing
屬性,一般不需要添加其它屬性。
現在運行app,當豎屏時,堆棧視圖的axis是垂直的;當橫屏時,堆棧視圖的axis是水平的。
4. 示例3
Stack View主要優點之一是會自動為其每個子視圖創建自動布局約束,也可以對這些子視圖的大小和位置進行設置。
為剛創建的demo添加一個View Controller,并連接到選項卡控制器,設定Bar Item的標題為Third
。
打開Third Scene,自上而下添加UILabel
、UIImageView
、UIButton
,并插入到一個垂直堆棧視圖中。堆棧視圖屬性設置如下:
- Axis: Vertical
- Alignment: Center
- Distribution: Equal Spacing
- Spacing:
0
其中UIImageView
的contentMode
為Aspect Fit,為上面Stack View添加約束,與Leading、Trailing、Top距離分別為0
、0
、20
。
在上圖Add Star下面添加一個水平堆棧視圖,屬性設置如下:
- Axis: Horizontal
- Alignment: Center
- Distribution: Fill Equally
- Spacing:
10
完成后為其添加約束,與Leading、Trailing、Top、Bottom距離分別為0
、0
、Standard Value
、20
,高度為120
。
雖然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按鈕插入堆棧視圖。修改剛插入堆棧視圖屬性如下:
- Axis: Horizontal
- Alignment: Center
- Distribution: Equal Spacing
- Spacing:
10
剛添加UIButton
標題為Remove Star
,文字顏色為red
。
為剛添加的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,現在可以添加、移除五角星,且視圖會隨著設備旋轉自動調整布局。
總結
通過這篇文章,可以看到UIStackView
極大降低了用戶界面開發難度,簡化了許多工作,僅僅添加少量約束便可實現自動布局。在布局時,應該優先考慮使用Stack View來布局界面。
Demo名稱:StackView
源碼地址:https://github.com/pro648/BasicDemos-iOS
參考資料: