距離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中找不到對(duì)應(yīng)的工具滑凉,而在iOS9中统扳,Apple就為我們添加了這樣的一個(gè)工具,它就是UIStackView畅姊。首先不要被名字所迷惑咒钟,以為是和UICollectionView、UITableView一樣一般作為最外層的容器View若未,雖然他也確實(shí)就是個(gè)容器View朱嘴。其實(shí)用一句話就可以概況它的本質(zhì):“自動(dòng)對(duì)一組橫向或豎向view集布局的容器view”。如果熟悉HTML的話粗合,可以類比"<div />" 不帶block屬性的就是橫向布局萍嬉,帶block組合的就是豎向布局。
UIStackView內(nèi)部是為其托管的子View添加Autolayout來實(shí)現(xiàn)其自動(dòng)布局的隙疚,所以要想更熟練的使用UIStackView壤追,最好能對(duì)AutoLayout有一定的理解,當(dāng)然供屉,如果對(duì)AutoLayout還不太熟悉大诸,也沒有關(guān)系,UIStackView的目的就是為使用者封裝這些復(fù)制的約束關(guān)系而存在的贯卦,只要看下面文章资柔,相信也能將UIStackView這一高效率組件運(yùn)用到自己的工程中。
和UICollectionView撵割、UITableView不一樣的是贿堰,UIStackView沒有繼承與UIScrollview而是直接繼承與UIView,所以對(duì)于超出屏幕的內(nèi)容啡彬,還需要自己用UIScrollView進(jìn)行交互布局羹与。雖然UIStackView是繼承與UIView故硅,但是卻沒有繼承UIView的渲染功能,所以UIStackView是沒有UI的纵搁,也就是不顯示本身的吃衅。所以類似“backgroundColor”的界面屬性就無效了,同時(shí)重寫 layerClass
, drawRect:
甚至drawLayer:inContext:
都是無效的腾誉。UIStackView是一個(gè)純粹的容器View徘层。
1. 最簡(jiǎn)單的一橫和一豎
說了這么多,到底要怎么使用呢利职?先來看個(gè)例子趣效,文中Demo都可以在Github找到:
在上面的例子中,包含兩個(gè)StackView布局(兩個(gè)淺藍(lán)色框):一個(gè)上面的橫向的猪贪,一個(gè)下面豎向的跷敬。
橫向的方框中有三個(gè)子圖片,豎向的方框中有四個(gè)子元素热押。這樣的布局要如何實(shí)現(xiàn)呢西傀?其實(shí)很簡(jiǎn)單,先來看在IB中的操作桶癣,就像平常一樣先拖三個(gè)圓圈圖片排成一排拥褂,然后按住“Command”鍵,選中三個(gè)圖片鬼廓,然后點(diǎn)擊Xcode的
“Editor” -> "Embed In" -> "Stack View"
會(huì)發(fā)現(xiàn)肿仑,三個(gè)圖片的位置被改動(dòng)了,緊貼在一起碎税,并且在IB中尤慰,看到三個(gè)圖片被一個(gè)新的“Stack View”包含了:
其實(shí)到這里就完成了一半需求了:有個(gè)容器View來管理一排子view。 現(xiàn)在在把目光放到IB的屬性界面雷蹂,來完成另一半
需求:
設(shè)置這樣的屬性伟端,Aligent為“Fill”,Distribution為“Equal Spacing”匪煌,Space為“8”责蝠。表示: 所有的子視圖豎直
方向填充滿StackView,也就是子view可能被拉伸到和StackView等高萎庭,每個(gè)子View之間等距間隔8 point霜医。有了這樣兩個(gè)約束也就能固定子View的布局了,從而實(shí)現(xiàn)對(duì)子View的AutoLayout驳规。
當(dāng)然除了對(duì)已有元素通過“Embed In”加入StackView肴敛,也可以從IB右下角拖一個(gè)StackView到面板中,比如這里拖動(dòng)一個(gè)豎直方向的“?StackView”到面板中,然后再從圖片里面拖幾個(gè)橫著的圖片到這里的豎直的StackView中医男,同時(shí)設(shè)置space為“8”砸狞,就可以完成上面的布局了。當(dāng)然镀梭,這里需要對(duì)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對(duì)應(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)來看:
這里第一排按鈕是Alignment,第二排按鈕是Distribution脓恕∧に危可以運(yùn)行Demo并體會(huì)不同。
這里Alignment主要控制垂直于StackView方向上的對(duì)其屬性炼幔,二Distribution則是控制在StackView延展方向的填充屬性:
下面看看總共都有哪些Alignment和Distribution秋茫。
Alignment
Alignment | 意義 | 效果 |
---|---|---|
UIStackViewAlignmentFill | 在StackView垂直方向上拉伸所有子view,使得填充完StackView | |
UIStackViewAlignmentLeading | 在StackView垂直方向上按照子view的leading edge對(duì)齊 | |
UIStackViewAlignmentTop | 等效UIStackViewAlignmentLeading,用于豎向Stackview | |
UIStackViewAlignmentFirstBaseline | 在StackView垂直方向上按照子view 的first baseline對(duì)其乃秀,僅適用于水平方向StackView | |
UIStackViewAlignmentCenter | 在StackView垂直方向上按照子View的中心線對(duì)其 | |
UIStackViewAlignmentTrailing | 在StackView垂直方向上按照子View的trailing edge對(duì)齊 | |
UIStackViewAlignmentBottom | 等效UIStackViewAlignmentTrailing,用于豎向Stackview | |
UIStackViewAlignmentLastBaseline | 在StackView垂直方向上按照子view 的last baseline對(duì)齊肛著,僅適用于水平方向StackView |
Distribution
Distribution | 意義 | 效果 |
---|---|---|
UIStackViewDistributionFill | 在StackView延伸方向上縮放子View使得子View能填充完StackView,子View的縮放順序依賴于其hugging優(yōu)先級(jí)跺讯,如果相等的話枢贿,則按照index順序 | |
UIStackViewDistributionFillEqually | 在StackView延伸方向上將每個(gè)子View都拉伸成一樣長 | |
UIStackViewDistributionFillProportionally | 在StackView延伸方向上將根據(jù)子View的內(nèi)容進(jìn)行縮放 | |
UIStackViewDistributionEqualSpacing | 在StackView延伸方向上將子View中間隔相等的空白進(jìn)行縮放,如果子View不夠大刀脏,則用空白填充開始部分局荚,如果子View過大,則根據(jù)hugging順序縮放火本,如果相等的話危队,則按照index順序 | |
UIStackViewDistributionEqualCentering | 在StackView延伸方向上將子View的中線線聪建,等距進(jìn)行縮放,如果子View不夠大茫陆,則用空白填充開始部分金麸,如果子View過大,則根據(jù)hugging順序縮放簿盅,如果相等的話挥下,則按照index順序 |
雖然上面羅列出來各個(gè)屬性的作用,但是可能還是不夠具體桨醋,這個(gè)還需要結(jié)合Demo或者自己在實(shí)際代碼中進(jìn)行設(shè)置來體驗(yàn)
3. 嵌套布局
上面的一橫一豎的例子棚瘟,在使用的時(shí)候,其實(shí)不用StackView也是非常容易用AutoLayout布局的喜最,那么怎么樣來提現(xiàn)StackView的優(yōu)勢(shì)呢偎蘸?如果把一橫一豎進(jìn)行各種組合,這樣就能像網(wǎng)頁設(shè)計(jì)中的"<div />"一樣進(jìn)行豐富的布局了瞬内,假設(shè)一個(gè)這樣的布局:
可以將其分解成各種橫豎的組合迷雪,從而得到如下的一個(gè)效果圖
在IB中可以很容易的拖拽實(shí)現(xiàn)StackView的嵌套,這里僅僅對(duì)最外層的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相比是不是很簡(jiǎn)單秦躯。
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只對(duì)應(yīng)了其中的顯示,但是并沒有消除其布局的影響呈础,所以還要調(diào)用StackView的:
- (void)removeArrangedSubview:(UIView *)view
取消其對(duì)布局的影響舆驶。
最后看個(gè)例子,點(diǎn)擊“贊”會(huì)增加星星而钞,點(diǎn)擊“貶”會(huì)減少星星數(shù)目:
布局很簡(jiǎn)單沙廉,主要是操作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在重新一遍以前的邏輯撬陵,畢竟上面舉例的場(chǎng)景,其實(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是對(duì)AutoLayout的一個(gè)封裝,其本身是和AutoLayout不沖突的(實(shí)際上就是新增了幾條約束)栖秕,所以熟練使用AutoLayout春塌,并和UIStackView配合,能夠?qū)崿F(xiàn)大量復(fù)雜的布局效果。