該文章屬于劉小壯原創(chuàng)鞍帝,轉(zhuǎn)載請注明:劉小壯
技術(shù)背景
前端的布局方式比較靈活,提供有Flex
的布局方式胎围,可以實(shí)現(xiàn)不同方向的彈性布局。Flex
就像一個容器壹粟,可以將其內(nèi)部的子控件統(tǒng)一進(jìn)行布局愕乎。其包含主軸方向和交叉軸方向,主軸方向表示控件的排布方向备禀,交叉軸方向和主軸方向相垂直洲拇。熟悉前端Flex
控件的同學(xué)應(yīng)該知道。Flex
有一些常用屬性曲尸,這里對其進(jìn)行簡單介紹赋续,方便了解UIStackView
的技術(shù)背景。
-
flex-direction
:控制Flex
的方向另患,`垂直還是水平纽乱; -
flex-wrap
:是否允許換行顯示,nowrap
表示單行顯示昆箕,nowarp
的場景下鸦列,如果控件過多,則可能導(dǎo)致子項(xiàng)等比縮小的問題为严; -
justify-content
:決定Flex
主軸方向上敛熬,子項(xiàng)對齊和分布方式,例如從前到后排列第股,或者等間距分布等应民; -
align-items
:決定Flex
交叉軸方向上,子項(xiàng)的對齊和分布方式夕吻,例如主軸設(shè)置為row
诲锹,交叉軸flex-start
就表示頂對齊,center
表示垂直居中對齊涉馅; -
align-content
:作用于交叉軸方向每行的對齊方式归园,屬性只針對于有換行的場景下有效。例如flex-start
是從上到下逐行排列稚矿,space-between
則表示每行間距相同庸诱,平均分配每行之間間距捻浦。
UIStackView介紹
在介紹完Flex
布局的背景之后,這里講一下UIStackView
控件桥爽。在iOS9
中蘋果在UIKit
框架中引入了一個新的視圖類UIStackView
朱灿,UIStackView
借鑒了Flex
的布局思想,采用線性布局的方式钠四,統(tǒng)一對子視圖進(jìn)行布局盗扒。只需要寫很少的Masonry
布局代碼,或者不寫布局代碼即可完成布局缀去。
UIStackView
已經(jīng)推出有一些年了侣灶,這里介紹這個控件,主要是出于兩個目的缕碎。一方面是對于沒使用過的同學(xué)褥影,介紹這個控件的功能以及適用的場景,項(xiàng)目中的一些場景通過UIStackView
處理起來會輕松很多阎曹。另一方面對于已經(jīng)使用過的同學(xué)伪阶,我會在文章中講一些我踩過的坑,以幫助大家更好的使用這個控件來解決業(yè)務(wù)中的問題处嫌。
需要注意的是栅贴,UIStackView
雖然繼承自UIView
,但是并不參與屏幕的渲染熏迹,所以重寫drawRect:
方法也是無效的檐薯。
核心參數(shù)
UIStackView
提供了兩個方向的布局,垂直布局和水平布局注暗,我們可以通過UILayoutConstraintAxis
枚舉值進(jìn)行設(shè)置坛缕。如果初學(xué)者來學(xué)習(xí)UIStackView
比較抽象,不太好接觸這個新的控件捆昏,而且很容易出錯赚楚,先看一下下面這個gif
來初步了解一下這個控件。
下面是一些UIStackView
的核心參數(shù)骗卜。
axis
-
horizontal
:水平方向布局宠页; -
vertical
:垂直方向布局;
distribution
-
fill
:填充整個UIStackView
寇仓,并且根據(jù)內(nèi)部子視圖尺寸進(jìn)行動態(tài)調(diào)整举户,可能會拉伸或壓縮某個子視圖,可以通過ContentHuggingPriority
和ContentCompressionResistancePriority
控制拉伸和壓縮的視圖遍烦,后面會講到俭嘁。
-
fillEqually
:子視圖等寬或等高,填充整個stackView
服猪,過程中會根據(jù)分配的大小改變子視圖尺寸供填。
-
fillProportionally
:根據(jù)子視圖intrinsicContentSize
按比例布局拐云。
-
equalSpacing
:等間距布局,如果stackView
放不下則會對子視圖進(jìn)行壓縮近她,默認(rèn)壓縮最后加入的子視圖慨丐。
-
equalCentering
:平均分配子視圖得到每個視圖的中心點(diǎn),使用這個中心點(diǎn)來布局每個子視圖泄私,并且保持spacing
距離,超出stackView
將會壓縮子視圖备闲。
alignment
-
fill
:交叉軸方向子視圖鋪滿晌端;
-
top
:子視圖頂對其(適用于horizontal
布局)
-
center
:子視圖居中對齊;
-
bottom
:子視圖底對齊(適用于horizontal
布局)
-
firstBaseline
:按照第一個子視圖的文字的第一行對齊恬砂,并且盡量保證子視圖底對齊(適用于horizontal
布局)
-
lastBaseline
:按照最后一個子視圖的最后一行對齊咧纠,并且盡量保證子視圖底對齊(適用于horizontal
布局)
-
trailing
:子視圖右對齊(適用于vertical
布局)
-
leading
:子視圖左對齊(適用于vertical
布局)
spacing
子控件之間的最小距離,根據(jù)下面的圖片結(jié)合上面的枚舉值聯(lián)系起來比較好理解泻骤。
基礎(chǔ)方法
UIStackView
的使用很多人都知道漆羔,這里舉一個簡單的例子,兩個UILabel
中間有一個分割線狱掂,并且整體居中對齊演痒。
對于傳統(tǒng)的Masonry
布局,需要寫不少布局代碼趋惨,而且為了整體居中鸟顺,需要在外面包一層UIView
進(jìn)行居中的布局。而使用UIStackView
器虾,布局代碼就下面這些讯嫂,單獨(dú)給中間的分割線指定個size
,就是唯一涉及Masonry
的處理兆沙。
[self.stackView addArrangedSubview:self.privacyButton];
[self.stackView addArrangedSubview:self.declareLineView];
[self.stackView addArrangedSubview:self.createContentDeclare];
[self.declareLineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(Pixel_1, 11.f));
}];
- (UIStackView *)stackView {
if (!_stackView) {
_stackView = [[UIStackView alloc] init];
_stackView.axis = UILayoutConstraintAxisHorizontal;
_stackView.alignment = UIStackViewAlignmentFill;
_stackView.spacing = 8;
}
return _stackView;
}
需要注意的是欧芽,UIStackView
添加子視圖,需要通過下面指定方法進(jìn)行葛圃,否則子視圖布局會有問題千扔。調(diào)用此方法后就不用再調(diào)用addSubview
方法,移除時直接調(diào)用removeFromSuperview
即可装悲。
- (void)addArrangedSubview:(UIView *)view;
使用進(jìn)階
隱藏子視圖
上面的布局場景很簡單昏鹃,但如果是下面這種復(fù)雜搜索欄的場景,就很適合用UIStackView
诀诊,通常搜索欄中的按鈕會要求某些條件下展示洞渤,某些條件下隱藏,同樣的搜索欄的寬度也是動態(tài)變化的属瓣。例如未登錄時沒有搜索欄右側(cè)的10086
客服電話按鈕载迄,搜索欄右側(cè)貼著消息按鈕的讯柔。
對于這種場景,只需要將10086
按鈕的hidden
設(shè)置為true
护昧,則可以實(shí)現(xiàn)上述效果魂迄。UIStackView
在檢測到某個子視圖被hidde
后,則這個子視圖不參與占位惋耙。
可以通過下面的動畫直觀的感受下hidden
的方便捣炬。
抗拉伸、抗壓縮
UIStackView
并不能完全替代Masonry
绽榛,除了其自帶的布局功能外湿酸,還需要處理子視圖的size
、抗拉伸灭美、抗擠壓推溃,以及stackView
其自身的布局處理。下面是兩個抗拉伸和抗壓縮的API
届腐。
設(shè)置抗拉伸級別铁坎。默認(rèn)值250
,控件級別設(shè)置越高犁苏,越不容易被拉伸硬萍。如果不設(shè)置優(yōu)先級,則排在最前的子視圖會被拉伸围详。
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;
設(shè)置抗壓縮級別襟铭。默認(rèn)值750
,控件級別設(shè)置越高短曾,越不容易被壓縮寒砖。如果不設(shè)置優(yōu)先級,則排在最后的子視圖會被壓縮嫉拐。如果修改最后的子視圖優(yōu)先級為defaultHigh
哩都,系統(tǒng)會找750
優(yōu)先級中最后的子視圖進(jìn)行壓縮。
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;
這里通過一個例子講一下抗拉伸的業(yè)務(wù)場景婉徘,抗壓縮也是同樣的道理漠嵌。假設(shè)有一個場景,一個horizontal
的stackView
有兩個UILabel
盖呼,這兩個label
的文案都是動態(tài)下發(fā)的儒鹿,產(chǎn)品需求是左側(cè)自適應(yīng)展示標(biāo)簽文案,右側(cè)文案展示不下打點(diǎn)几晤。
這種場景如果用布局來做會很復(fù)雜约炎,增加很多計算操作。用UIStackView
實(shí)現(xiàn)就很簡單,我們需要保證的是不拉伸左側(cè)label
圾浅,只需要給左邊label
設(shè)置抗拉伸優(yōu)先級高一些即可實(shí)現(xiàn)掠手。
tagLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
不足點(diǎn)
相對于前端的Flex
布局,UIStackView
對某個子視圖可做的處理很有限狸捕。Flex
除了可以對容器自身進(jìn)行布局設(shè)置喷鸽,即開頭介紹的部分,也可以對Flex
布局中的子視圖進(jìn)行單獨(dú)布局設(shè)置灸拍。下面是一些Flex
對子視圖的一些實(shí)用屬性做祝,可以了解下。
-
order
:Flex
的每個子視圖order
默認(rèn)都是0
鸡岗,如果想讓某個視圖排列在最前面剖淀,可以將order
設(shè)置為-1
,同樣的纤房,如果想讓子視圖展示在最后,可以將order
設(shè)置為1
翻诉; -
flex-grow
:可以設(shè)置某個子視圖的擴(kuò)展炮姨,擴(kuò)展指的是這個子視圖是否占據(jù)除元素外的空白區(qū)域,取值范圍是0
到1
碰煌,例如設(shè)置為1
則表示占據(jù)全部空白區(qū)域舒岸; -
flex-basis
:通過這個屬性可以設(shè)置某個子視圖占據(jù)多少寬度,默認(rèn)是auto
由瀏覽器分配寬度芦圾,如果設(shè)置的寬度超過父視圖蛾派,則子視圖會進(jìn)行等比收縮;