接受新時(shí)代的UIStackView

距離iOS9發(fā)布已經(jīng)接近一年了,我們即將引來新的iOS 10,為何在這個(gè)時(shí)候來介紹iOS9中新引入的一個(gè)布局組件呢?猶如當(dāng)年的AutoLayout剛推出來一樣,一來文檔少、二來操作繁瑣,最重要的要是兼容之前的系統(tǒng),用新技術(shù)擼一邊等于是多做工。如今在LinkdedIn已經(jīng)要求從iOS8開始的時(shí)代(QQ/微信/微博/淘寶均要求>=iOS7),可以預(yù)見iOS10發(fā)布后不久的將來iOS9也將成為最低要求,其帶來的一些高效率工具(比如這里要介紹的UIStackView)也必將成為主流。

如果有Android相關(guān)開發(fā)經(jīng)驗(yàn),或者從Android開發(fā)轉(zhuǎn)到iOS開發(fā),會(huì)發(fā)現(xiàn)Android4就引入的可以解決多屏幕適配Linerlayout/RelativeLayout在iOS中找不到對應(yīng)的工具,而在iOS9中,Apple就為我們添加了這樣的一個(gè)工具,它就是UIStackView。首先不要被名字所迷惑,以為是和UICollectionView、UITableView一樣一般作為最外層的容器View,雖然他也確實(shí)就是個(gè)容器View。其實(shí)用一句話就可以概況它的本質(zhì):“自動(dòng)對一組橫向或豎向view集布局的容器view”。如果熟悉HTML的話,可以類比"<div />" 不帶block屬性的就是橫向布局,帶block組合的就是豎向布局。

UIStackView內(nèi)部是為其托管的子View添加Autolayout來實(shí)現(xiàn)其自動(dòng)布局的,所以要想更熟練的使用UIStackView,最好能對AutoLayout有一定的理解,當(dāng)然,如果對AutoLayout還不太熟悉,也沒有關(guān)系,UIStackView的目的就是為使用者封裝這些復(fù)制的約束關(guān)系而存在的,只要看下面文章,相信也能將UIStackView這一高效率組件運(yùn)用到自己的工程中。

和UICollectionView、UITableView不一樣的是,UIStackView沒有繼承與UIScrollview而是直接繼承與UIView,所以對于超出屏幕的內(nèi)容,還需要自己用UIScrollView進(jìn)行交互布局。雖然UIStackView是繼承與UIView,但是卻沒有繼承UIView的渲染功能,所以UIStackView是沒有UI的,也就是不顯示本身的。所以類似“backgroundColor”的界面屬性就無效了,同時(shí)重寫 layerClass, drawRect:甚至drawLayer:inContext:都是無效的。UIStackView是一個(gè)純粹的容器View。

1. 最簡單的一橫和一豎

說了這么多,到底要怎么使用呢?先來看個(gè)例子,文中Demo都可以在Github找到:

signal_demo

signal_demo_plan

在上面的例子中,包含兩個(gè)StackView布局(兩個(gè)淺藍(lán)色框):一個(gè)上面的橫向的,一個(gè)下面豎向的。

橫向的方框中有三個(gè)子圖片,豎向的方框中有四個(gè)子元素。這樣的布局要如何實(shí)現(xiàn)呢?其實(shí)很簡單,先來看在IB中的操作,就像平常一樣先拖三個(gè)圓圈圖片排成一排,然后按住“Command”鍵,選中三個(gè)圖片,然后點(diǎn)擊Xcode的

“Editor” -> "Embed In" -> "Stack View"

會(huì)發(fā)現(xiàn),三個(gè)圖片的位置被改動(dòng)了,緊貼在一起,并且在IB中,看到三個(gè)圖片被一個(gè)新的“Stack View”包含了:

ib_signal_layer

其實(shí)到這里就完成了一半需求了:有個(gè)容器View來管理一排子view。 現(xiàn)在在把目光放到IB的屬性界面,來完成另一半
需求:

ib_signal_attr

設(shè)置這樣的屬性,Aligent為“Fill”,Distribution為“Equal Spacing”,Space為“8”。表示: 所有的子視圖豎直
方向填充滿StackView,也就是子view可能被拉伸到和StackView等高,每個(gè)子View之間等距間隔8 point。有了這樣兩個(gè)約束也就能固定子View的布局了,從而實(shí)現(xiàn)對子View的AutoLayout。

當(dāng)然除了對已有元素通過“Embed In”加入StackView,也可以從IB右下角拖一個(gè)StackView到面板中,比如這里拖動(dòng)一個(gè)豎直方向的“?StackView”到面板中,然后再從圖片里面拖幾個(gè)橫著的圖片到這里的豎直的StackView中,同時(shí)設(shè)置space為“8”,就可以完成上面的布局了。當(dāng)然,這里需要對StackView自己做一些AutoLayout的設(shè)置,從而確定容器的布局(也就是位置和大小),然后StackView才能結(jié)合屬性確定其內(nèi)部子View的布局。

當(dāng)然除了使用IB也可以通過代碼來創(chuàng)建StackView:

UIImageView *star1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dynamic_start"]];
star1.contentMode = UIViewContentModeScaleToFill;
UIImageView *star2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dynamic_start"]];
star2.contentMode = UIViewContentModeScaleToFill;
UIImageView *star3 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dynamic_start"]];
star2.contentMode = UIViewContentModeScaleToFill;
UIStackView *starStackView = [[UIStackView alloc] initWithArrangedSubviews:@[star1, star2, star3]];
starStackView.axis = UILayoutConstraintAxisHorizontal;
starStackView.alignment =  UIStackViewAlignmentFill
starStackView.distribution = UIStackViewDistributionEqualSpacing
starStackView.spacing = 8.0

上面的代碼也實(shí)現(xiàn)了一排橫向布局,其中axis控制了水平方向還是豎直方向,alignment、distribution以及spacing對應(yīng)IB里面的屬性設(shè)置。

2. 修改屬性定制StackView

在上面的IB屬性欄中,可以看到,StackView的屬性其實(shí)少的可憐,圖中就四個(gè)可以設(shè)置(其實(shí)也確實(shí)就這四個(gè)加上個(gè)子view的數(shù)組)。這里的"Axis"比較好理解,就是控制是一橫還是一豎,容器的方向。同樣的"Spacing"也比較好理解,就是壘在一起的子view之間的距離。但是這個(gè)"Alignment"和"Distribution"又是什么呢?我們來通過例子中的"Attr" Tab中的按鈕選項(xiàng)來看:

attr_demo_1

attr_demo_2

這里第一排按鈕是Alignment,第二排按鈕是Distribution。可以運(yùn)行Demo并體會(huì)不同。

這里Alignment主要控制垂直于StackView方向上的對其屬性,二Distribution則是控制在StackView延展方向的填充屬性:

attr

下面看看總共都有哪些Alignment和Distribution。

Alignment

Alignment 意義 效果
UIStackViewAlignmentFill 在StackView垂直方向上拉伸所有子view,使得填充完StackView
align_fill
UIStackViewAlignmentLeading 在StackView垂直方向上按照子view的leading edge對齊
align_leading
UIStackViewAlignmentTop 等效UIStackViewAlignmentLeading,用于豎向Stackview
align_top
UIStackViewAlignmentFirstBaseline 在StackView垂直方向上按照子view 的first baseline對其,僅適用于水平方向StackView
align_first_baseline
UIStackViewAlignmentCenter 在StackView垂直方向上按照子View的中心線對其
align_center
UIStackViewAlignmentTrailing 在StackView垂直方向上按照子View的trailing edge對齊
align_trailing
UIStackViewAlignmentBottom 等效UIStackViewAlignmentTrailing,用于豎向Stackview
align_bottom
UIStackViewAlignmentLastBaseline 在StackView垂直方向上按照子view 的last baseline對齊,僅適用于水平方向StackView
align_last_baseline

Distribution

Distribution 意義 效果
UIStackViewDistributionFill 在StackView延伸方向上縮放子View使得子View能填充完StackView,子View的縮放順序依賴于其hugging優(yōu)先級,如果相等的話,則按照index順序
dist_fill
UIStackViewDistributionFillEqually 在StackView延伸方向上將每個(gè)子View都拉伸成一樣長
dist_fill_equally
UIStackViewDistributionFillProportionally 在StackView延伸方向上將根據(jù)子View的內(nèi)容進(jìn)行縮放
dist_fill_proportionally
UIStackViewDistributionEqualSpacing 在StackView延伸方向上將子View中間隔相等的空白進(jìn)行縮放,如果子View不夠大,則用空白填充開始部分,如果子View過大,則根據(jù)hugging順序縮放,如果相等的話,則按照index順序
dist_equal_spacing
UIStackViewDistributionEqualCentering 在StackView延伸方向上將子View的中線線,等距進(jìn)行縮放,如果子View不夠大,則用空白填充開始部分,如果子View過大,則根據(jù)hugging順序縮放,如果相等的話,則按照index順序
dist_equal_centering

雖然上面羅列出來各個(gè)屬性的作用,但是可能還是不夠具體,這個(gè)還需要結(jié)合Demo或者自己在實(shí)際代碼中進(jìn)行設(shè)置來體驗(yàn)

3. 嵌套布局

上面的一橫一豎的例子,在使用的時(shí)候,其實(shí)不用StackView也是非常容易用AutoLayout布局的,那么怎么樣來提現(xiàn)StackView的優(yōu)勢呢?如果把一橫一豎進(jìn)行各種組合,這樣就能像網(wǎng)頁設(shè)計(jì)中的"<div />"一樣進(jìn)行豐富的布局了,假設(shè)一個(gè)這樣的布局:

nested_design

可以將其分解成各種橫豎的組合,從而得到如下的一個(gè)效果圖

nested_demo

nested_effect

在IB中可以很容易的拖拽實(shí)現(xiàn)StackView的嵌套,這里僅僅對最外層的StackView做了大小和位置設(shè)置,其他子View均是由StackView來自動(dòng)控制的。

右上角是一個(gè)豎向的StackView(假設(shè)名字為A)并設(shè)置Aligment為"Leading/Top",Distrubition為"Equal Centering"。然后以A為整體,在和圖片一起放到一個(gè)橫向的StackView(假設(shè)名字為B)中,并設(shè)置Aligment為"Top",Distrubition為"Fill Equal"。最后將這個(gè)B和下面的大圖和文字TextView放到一個(gè)豎向的StackView中,并設(shè)置Aligment為"Leading/Top",Distrubition為"Fill Equal"。這樣就通過嵌套StackView完成了一個(gè)復(fù)雜布局了,和要多每個(gè)子view都要設(shè)置AutoLayout相比是不是很簡單。

4. 不用datasource的動(dòng)態(tài)布局

在使用UITableView或者UICollectionView等容器View的時(shí)候,通常都會(huì)有個(gè)datasource來動(dòng)態(tài)的填充其中的容納的內(nèi)容,但是同樣作為容器View的StackView卻沒有這樣的datasoure,他就只用一個(gè)數(shù)組和“add/remove”方法來管理其容納的子view。

最開始的 - (instancetype)initWithArrangedSubviews:(NSArray<__kindofUIView *> *)views展示了怎么用一個(gè)數(shù)組初始化StackView了,數(shù)組中的view會(huì)依照其順序添加到容器View StackView中,其包括兩個(gè)部分,一部分是將布局托管給StackView設(shè)置,另一方面會(huì)調(diào)用addSubView,添加到StackView中顯示。

如果需要添加新的子View可以調(diào)用:

- (void)addArrangedSubview:(UIView *)view 

將一個(gè)新的view托管給StackView進(jìn)行布局,并addSubView進(jìn)行顯示。這里新的view會(huì)別追加到已有子view的后面,如果想插入到中間,可以使用:

  • (void)insertArrangedSubview:(UIView *)view
    atIndex:(NSUInteger)stackIndex

如果不想顯示一個(gè)子view要怎么操作呢?當(dāng)然調(diào)用子View的“removeFromSuperview”,但是這樣就夠了么?上面說了兩步,這個(gè)remove只對應(yīng)了其中的顯示,但是并沒有消除其布局的影響,所以還要調(diào)用StackView的:

- (void)removeArrangedSubview:(UIView *)view

取消其對布局的影響。

最后看個(gè)例子,點(diǎn)擊“贊”會(huì)增加星星,點(diǎn)擊“貶”會(huì)減少星星數(shù)目:

dynamic_demo

布局很簡單,主要是操作StackView的增減子view:

@interface DynamicVC ()

@property (weak, nonatomic) IBOutlet UIStackView *starStackView;

@end

@implementation DynamicVC

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder  {
    if ( self = [super initWithCoder:aDecoder]) {
        self.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Dynamic" image:[UIImage imageNamed:@"test"] selectedImage:[UIImage imageNamed:@"test"]];
    }
    return  self;
}
- (IBAction)onUp:(id)sender {
    UIImageView *star = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dynamic_start"]];
    star.contentMode = UIViewContentModeScaleToFill;
    [self.starStackView addArrangedSubview:star];

    [UIView animateWithDuration:1.0 animations:^{
        [self.starStackView layoutIfNeeded];
    }];
}

- (IBAction)onDown:(id)sender {
    UIImageView *star = [self.starStackView.arrangedSubviews lastObject];
    [self.starStackView removeArrangedSubview:star];
    [star removeFromSuperview];
    [UIView animateWithDuration:1.0 animations:^{
        [self.starStackView layoutIfNeeded];
    }];
}

5. 總結(jié)

在當(dāng)前的產(chǎn)品中,可能會(huì)考慮到兼容以前的版本,不會(huì)考慮用UIStackView在重新一遍以前的邏輯,畢竟上面舉例的場景,其實(shí)不用UIStackView,也是有很成熟的
方法進(jìn)行布局,而且基本都被大家運(yùn)用在產(chǎn)品中,經(jīng)過了生產(chǎn)環(huán)境的驗(yàn)證。但是了解了UIStackView,在日后做Demo的時(shí)候,可以為布局節(jié)省很多精力,并且也可以
為未來iOS9成為最低配時(shí)積累經(jīng)驗(yàn),在未來的產(chǎn)品中用更高效的工具進(jìn)行布局,節(jié)省耗在布局上的時(shí)間。所以還是推薦大家在iOS10即將出生之際學(xué)習(xí)下這個(gè)新時(shí)代的
布局工具。

UIStackView其實(shí)很好理解,就是一橫一豎的關(guān)系,但是通過調(diào)節(jié)其屬性(UIStackViewDistribution和UIStackViewAlignment)可以透明的運(yùn)用Auto?Layout帶來強(qiáng)大的自動(dòng)布局功能。通過自己多聯(lián)系嘗試不同屬性的組合,積累經(jīng)驗(yàn),這樣才能在需要的時(shí)候,快速的用UIStackView處理以前需要很多步驟
(比如各種Autolayout約束、用UICollectionView或者UITableView)才能搞定的布局。

另外UIStackView是對AutoLayout的一個(gè)封裝,其本身是和AutoLayout不沖突的(實(shí)際上就是新增了幾條約束),所以熟練使用AutoLayout,并和UIStackView配合,能夠?qū)崿F(xiàn)大量復(fù)雜的布局效果。

參考

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

推薦閱讀更多精彩內(nèi)容