Auto Layout中Stack View的使用

這是關于自動布局的第二篇文章。
<< Auto Layout的使用

上一篇文章介紹了如何使用Auto Layout,這一篇文章主要介紹堆棧視圖(Stack View)Stack View提供了一種輕松的方式來使用Auto Layout,不需要引入復雜的約束。單個堆棧視圖定義用戶界面的行或列,堆棧視圖根據以下屬性來排列其子視圖。

  • axis:定義Stack View的方向,水平方向或豎直方向,只適用于UIStackViewNSStackView中定義方向使用orientation屬性。
  • distribution:設定視圖沿軸線的排布方式。
  • alignment:設定如何沿軸線垂直方向排布子視圖。
  • spacing:設定子視圖間距。
UIStackViewProperties.png

UIStackView適用于iOS 9.0+和tvOS 9.0+,NSStackView適用于macOS 10.9+。這篇文章只講解UIStackView

使用Stack View時可以先從對象庫中拖拽出Horizontal Stack ViewVertical Stack Viewstoryboard,后把需要添加的視圖放進Stack View;也可以先添加視圖,后點擊Auto Layout工具Embed in Stack,Auto Layout會根據視圖布局插入水平或垂直堆棧視圖,也可以點擊Editor > Embed In > Stack View插入堆棧視圖,與點擊Embed In Stack插入沒有區別。

1. 創建demo

這里使用Tabbed Application的模板創建demo,Product NameStackView,選擇文件位置,創建工程。

下面通過三個示例來學習Stack View

2. 示例1

打開剛創建demo的Main.storyboard,在First Scene添加以下視圖:第一行為UILabelUISwitch,第二行為兩個UIImageView

StackViewD1Storyboard.png

選中第一行的UILabelUISwitch,點擊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距離為20points。如果添加過程遇到問題,你可以點擊這里查看demo,也可以查看上一篇文章學習如何添加約束。添加完畢后視圖層級如下:

StackViewD1Hierarchy.png

使用Stack View時一般只需要指定堆棧視圖位置,堆棧視圖大小會根據子視圖大小動態調整。

堆棧視圖中子視圖顯示順序由其在arrangedSubviews數組的順序決定。在水平堆棧視圖中,視圖顯示方向與閱讀方向一致,arrangedSubviews數組中低索引號視圖先顯示。在垂直堆棧視圖中,視圖從上向下顯示,低索引號視圖在上,高索引號視圖在下。

當向arrangedSubviews數組中添加、移除視圖時,或視圖被隱藏時,Stack View會自動調整布局。

下面從UISwitch連接出一個IBAction的屬性,當點擊UISwitch時,調整imageStackViewaxisimageStackViewstoryboard中圖片堆棧視圖的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,圖片分別為HeartStar,可以通過文章底部網址下載源碼獲取圖片。選中兩個UIImageView插入一個Horizontal堆棧視圖。堆棧視圖與LeadingTrailingTopBottom距離分別為00Standard ValueStandard ValueStack View其它屬性如下:

  • Axis:Horizontal
  • Alignment:Center
  • Distribution:Fill Proportionally
  • Spacing:0
StackViewD2Stroyboard.png

上面的DistributionFillFill EquallyFill ProportionallyEqual SpacingEqual Centering五個屬性,這五個屬性的區別:

  • UIStackViewDistributionFillStack View調整子視圖大小以便填充所有可用空間。當子視圖大小大于可用空間時,根據Compression Resistance優先級壓縮視圖;當子視圖不能填充滿可用空間時,根據Content Hugging優先級拉伸視圖。如果優先級相同,優先調整arrangedSubviews數組中index小的視圖。
  • UIStackViewDistributionFillEqually:調整所有子視圖為相同大小,占用所有可用空間。
  • UIStackViewDistributionFillProportionally:會保持每一個子視圖的固有大小,但如果有可用空間、或需要壓縮視圖,會按比例拉伸、壓縮。如一個視圖寬為100,另一個視圖寬為200Stack View想要拉伸視圖以便填充可用空間,第一個視圖寬被拉伸為150,第二個視圖寬就會被拉伸為300
  • UIStackViewDistributionEqualSpacing:不調整子視圖大小,通過移動子視圖位置讓子視圖間距相等。如果子視圖大于可用空間,會按照Compression Resistance優先級壓縮。如果優先級相同,優先調整arrangedSubviews數組中index小的視圖。
  • UIStackViewDistributionEqualCentering:讓子視圖的中心距離相等。如果視圖大于可用空間,會壓縮spacing屬性直到設定的最小值,如果此時仍舊大于可用空間,會根據子視圖Compression Resistance優先級壓縮子視圖。如果優先級相同,優先調整arrangedSubviews數組中index小的視圖。

選中Stack View,打開Attributes Inspector,你會發現每一個Stack View屬性前會有一個+

StackViewD2Attributes.png

點擊這些+,可以自定義水平、垂直堆棧視圖的屬性。下面添加一個當寬度為compact、高度為regularaxis為垂直的屬性。

StackViewD2AttributesAxis.png

你還可以通過相同方式,添加一個當寬度為compact、高度為regularspacing屬性,一般不需要添加其它屬性。

現在運行app,當豎屏時,堆棧視圖的axis是垂直的;當橫屏時,堆棧視圖的axis是水平的。

StackViewAxis.gif

4. 示例3

Stack View主要優點之一是會自動為其每個子視圖創建自動布局約束,也可以對這些子視圖的大小和位置進行設置。

為剛創建的demo添加一個View Controller,并連接到選項卡控制器,設定Bar Item的標題為Third

打開Third Scene,自上而下添加UILabelUIImageViewUIButton,并插入到一個垂直堆棧視圖中。堆棧視圖屬性設置如下:

  • Axis: Vertical
  • Alignment: Center
  • Distribution: Equal Spacing
  • Spacing: 0
StackViewD3Stroyboard.png

其中UIImageViewcontentModeAspect Fit,為上面Stack View添加約束,與LeadingTrailingTop距離分別為0020

在上圖Add Star下面添加一個水平堆棧視圖,屬性設置如下:

  • Axis: Horizontal
  • Alignment: Center
  • Distribution: Fill Equally
  • Spacing: 10

完成后為其添加約束,與LeadingTrailingTopBottom距離分別為00Standard Value20,高度為120

StackViewD3StoryboardFinal.png

雖然UIStackView繼承自UIView,但它只管理子視圖的位置和大小,不提供用戶界面,也就是有些屬性不適用于UIStackView,如backgroundColor,也不能重寫drawRect:方法。

堆棧視圖中有subviewsarrangedSubviews兩個屬性,其遵守以下規則。

  • Stack ViewarrangedSubviews數組添加視圖時,也會將該視圖添加為subviews
  • Stack View移除一個視圖,該視圖也會被從arrangedSubviews數組移除。
  • arrangedSubviews數組移除一個視圖,該視圖依然存在于subviews數組。Stack View不在管理被移除視圖的位置和大小,但它會存在于視圖層級中。

所以,通過調用addArrangedSubview:insertArrangedSubview: atIndex:方法向arrangedSubviews數組添加元素,該元素同時會被加入subviews數組。通過removeFromSuperView方法移除的視圖,會被同步從arrangedSubviews數組移除。通過removeArrangedSubview:移除的視圖,還會存在于subviews數組。

UIButton連接出一個名為addStarIBAction點擊事件。當點擊時,在底部的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];
    }];
}

現在增加移除五角星的功能。從對象庫拖拽一個UIButtonAdd Star下方。選中UIButtonAdd Star,點擊Embed In Stack按鈕插入堆棧視圖。修改剛插入堆棧視圖屬性如下:

  • Axis: Horizontal
  • Alignment: Center
  • Distribution: Equal Spacing
  • Spacing: 10

剛添加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

參考資料:

  1. Stacks
  2. iOS 9: Getting Started with UIStackView
  3. What are the different UIStackView distribution types?

歡迎更多指正:https://github.com/pro648/tips/wiki

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容