iOS有三種基本的界面布局的方法,分別是手寫UI,xib和storyboard。手寫UI是最早進(jìn)行UI界面布局的方法案铺,優(yōu)點是靈活自由,缺點是需要寫大段的代碼進(jìn)行布局梆靖。xib也是比較早出現(xiàn)的UI布局的方式控汉,優(yōu)點是不需要手寫代碼,但是每個界面對應(yīng)一個xib返吻,管理起來復(fù)雜姑子。而storyboard則是在iOS5以后出現(xiàn)的,是蘋果官方主推的一個代替xib的策略测僵,不僅能將xib匯總統(tǒng)一管理壁酬,還可以描述各種場景之間的過渡,缺點是多人協(xié)作開發(fā)時容易產(chǎn)生沖突恨课。
下面主要介紹的是手寫頁面布局舆乔。
一、AutoresizingMasks
可以使用 AutoresizingMasks 進(jìn)行頁面布局剂公,在 UIView 中有一個autoresizingMask的屬性希俩,它對應(yīng)的是一個枚舉的值,屬性的意思就是自動調(diào)整子控件與父控件中間的位置纲辽,寬高颜武。默認(rèn)值是UIViewAutoresizingNone,控件不會隨父視圖的改變而改變拖吼。
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0, // 自動調(diào)整view與父視圖左邊距鳞上,以保證右邊距不變
UIViewAutoresizingFlexibleWidth = 1 << 1, // 自動調(diào)整view的寬度,保證左邊距和右邊距不變
UIViewAutoresizingFlexibleRightMargin = 1 << 2, // 自動調(diào)整view與父視圖右邊距吊档,以保證左邊距不變
UIViewAutoresizingFlexibleTopMargin = 1 << 3, // 自動調(diào)整view與父視圖上邊距篙议,以保證下邊距不變
UIViewAutoresizingFlexibleHeight = 1 << 4, // 自動調(diào)整view的高度,以保證上邊距和下邊距不變
UIViewAutoresizingFlexibleBottomMargin = 1 << 5 // 自動調(diào)整view與父視圖的下邊距,以保證上邊距不變
}
AutoresizingMasks是對未來變化的一種預(yù)期鬼贱,系統(tǒng)會生成frame的布局,當(dāng)遇到需要使用到多個值的場景時移怯,支持使用|操作符。
例如这难,需要設(shè)置播放器浮層隨播放器大小變化:
UIView *overlay = [[UIView alloc] init];
overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:overlay];
Autoresizing需要注意的是舟误,storyboard中設(shè)置的約束和手寫代碼中設(shè)置的約束是相反的。storyboard 圖形頁面里點的右邊的線和下邊的線的意思是“固定”姻乓。
二嵌溢、Frame
frame指的是當(dāng)前視圖在其父視圖中的位置和大小。
在初始化 view 的時候蹋岩,可以設(shè)置 view 的frame
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 30, 40)];
初始化一個距離父視圖左邊距10堵腹,上邊距20,寬30星澳,高40的視圖。也可以修改聲明View的位置以及大小旱易。
view.frame = CGRectMake(20, 10, 40, 30);
設(shè)置和修改視圖的 frame 可以完成對界面的布局禁偎。
2.1 bounds
提到 frame 不得不提 bounds,bounds指的是前視圖在其自身坐標(biāo)系統(tǒng)中的位置和大小阀坏∪缗可以看到兩者的區(qū)別在于坐標(biāo)系不同。
2.2 layoutSubviews
需要重新布局視圖可以使用 layoutSubviews
1)可以在view里重寫layoutSubviews
2)可以在view controller里使用
viewWillLayoutSubviews 在autoresizingMasks前調(diào)用
viewDidLayoutSubviews 在autoresizingMasks后調(diào)用忌堂,肯定會覆蓋autoresizingMasks的結(jié)果
layoutSubviews可能會在不需要調(diào)用的時候調(diào)用盒至,如果layoutSubviews的比較復(fù)雜,可能會卡頓
三士修、自動布局AutoLayout
前面講到的 frame 主要用于視圖的絕對位置枷遂,但是 iOS 設(shè)備有多個尺寸,如何對不同尺寸進(jìn)行適應(yīng)棋嘲,蘋果的解決方案是使用 AutoLayout酒唉。
如果是從代碼層面開始使用 Autolayout,需要對使用的 View 的translatesAutoresizingMaskIntoConstraints 的屬性設(shè)置為NO沸移。即可開始通過代碼添加Constraint痪伦,否則View還是會按照以往的autoresizingMask進(jìn)行計算。而在 Interface Builder 中勾選了Use Auto layout雹锣,translatesAutoresizingMaskIntoConstraints 屬性都會被默認(rèn)設(shè)置NO网沾。
3.1 約束
自動布局里最重要的組成部分就是約束。分別可以設(shè)置視圖相對于另一個視圖的 leading蕊爵、trailing辉哥、top、bottom攒射、CenterX证薇、CenterY 等關(guān)系度苔。根據(jù)這些約束來確定視圖的相對位置。
視圖的約束之間的關(guān)系為線型關(guān)系浑度。例如寇窑,視圖Y 相對于 視圖X 的位置可以表示為一個線性變換,即
Y = kX + b
即 Y 是 X 某個方向坐標(biāo)或大小的 k 倍并偏移 b箩张。k 和 b 的大小可以是0甩骏。如果 k = 1, b = 0先慷, 則表示 Y 和 X 分別表示視圖的寬饮笛,則等式表示 Y 和 X 的寬度相等。
AutoLayout 的核心是:Every view requires at least two constraints along each axis to set position and size. 即在每個坐標(biāo)軸上至少需要2個約束來確定視圖位置论熙。
3.2 Ambiguous Layout
在開發(fā)過程中福青,你可以通過調(diào)用hasAmbiguousLayout 來測試你的view約束是否足夠的。函數(shù)會返回布爾值脓诡。如果有一個不同的frame就會返回yes无午,如果view的約束完全指定了就會返回no。
一個設(shè)定了完全約束的view的子view也可能存在ambiguous layout祝谚,需要為每一個view單獨測試layout是否存在ambiguous layout宪迟。
3.3 Intrinsic Content Size
使用autolayout時,view的content扮演著非常重要的角色交惯。每個view的intrinsicContentSize描述了不會剪切的顯示完整view content的最小空間次泽。例如一個image view,content size根據(jù)image顯示的size設(shè)置席爽。一個大的image需要一個大的固有的content size意荤。image的大小提供給了view。
對于button只锻,固有的content size根據(jù)他的title而有不同袭异。隨著title增長或者縮短,button的固有的content size也會調(diào)節(jié)來做適應(yīng)炬藤,可以根據(jù)你自定義的font size和title text而有變化御铃。
3.4 Compression Resistance and Content Hugging
3.4.1 compression resistance
壓縮阻力表示一個視圖的抗壓縮性。一個有高compression resistance的視圖會防止被壓縮沈矿。也不會允許content被裁剪上真,而會嘗試保存他的最小固有content size。
autolayout經(jīng)常遇到兩個沖突的請求羹膳。當(dāng)只有一個請求會成功時睡互,他就會滿足高優(yōu)先級的那個。可以分別設(shè)置水平和垂直方向的Compression Resistance就珠。value從1(最低)到1,000(請求的優(yōu)先級)不等寇壳。默認(rèn)的是750。
[button setContentCompressionResistancePriority:500 forAxis:UILayoutConstraintAxisHorizontal];
3.4.2 content hugging
抗拉屬性表示view防止被拉伸的屬性妻怎,和壓縮阻力類似壳炎。默認(rèn)值為250。
[button setContentHuggingPriority:501 forAxis:UILayoutConstraintAxisHorizontal];
3.5 VFL
Visual Format Language逼侦,即“可視化格式語言”匿辩。直接手寫約束很復(fù)雜,使用VFL相對簡單很多榛丢,但比較難進(jìn)行調(diào)試铲球。
[self.view addConstraints: [NSLayoutConstraint
constraintsWithVisualFormat:@"V:[view1]-8-[view2]"
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:NSDictionaryOfVariableBindings(view1, view2)]];
3.6 Masonry
VFL的寫法也相當(dāng)復(fù)雜,可以使用第三方框架 Masonry晰赞,Masonry 是一個輕量級的布局框架稼病,Masonry 源碼:https://github.com/Masonry/Masonry
例如,設(shè)置view1相對父View的每個邊距離為padding:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
需要注意的是掖鱼,在結(jié)構(gòu)一樣的情況下用mas_updateConstraints然走,會更新當(dāng)前的約束,但是如果要覆蓋緣由約束重新添加锨用,則需要使用方法用mas_remakeConstraints
constraint加到兩個view的公共父view上,因此有一個奇怪的現(xiàn)象是一個view不持有自己的約束隘谣,而被其他view持有增拥。在 Masonry 中,實際添加constraint的不一定是約束的持有者
四寻歧、更新布局方法
設(shè)置好約束以后掌栅,布局是如何更新的呢?
Constraints
- (void)updateConstraintsIfNeeded // 立即重新計算約束码泛,如果在這之前addConstraints猾封,就可以更新約束
- (void)setNeedsUpdateConstraints // 立即返回,標(biāo)記說需要改變約束值噪珊,在當(dāng)前update cycle結(jié)束后更新之前所有標(biāo)記過要改變的約束晌缘,調(diào)用updateConstraints方法
Layout
- (void)layoutIfNeeded // 立即更新布局,重新計算約束痢站,如果在這之前addConstraints就會立即反應(yīng)在頁面上
- (void)setNeedsLayout // 同Constraints磷箕,不過是更新布局
- (void)layoutSubviews // 布局當(dāng)前頁面的子頁面
Draw
- (void)setNeedsDisplay // 同Constraints,不過是重新渲染
4.1 Constraints,Layout阵难,Draw調(diào)用順序
一個頁面更新的順序一般為
調(diào)用約束計算出frame(Constraints)→ 根據(jù)計算出的frame重新布局(Layout) → 根據(jù)重新布局的結(jié)果進(jìn)行圖像渲染(Draw)
layout的改變會導(dǎo)致重新計算Constraints
layoutIfNeeded會調(diào)用updateConstraintsIfNeeded
Constraints的計算順序是低到上 (從subview到superview)
layout的更新順序是從頂?shù)较拢◤膕uperview到subview)
4.2 frame和約束區(qū)別
1岳枷、frame是不可以累加的,只能被替換掉,但是constraint可以累加
2空繁、frame不可以跨級添加殿衰,但是contraint可以跨層級
3、contraint可以設(shè)定priority
另外盛泡,需要注意的是闷祥,在autolayout下使用frame,會把frame轉(zhuǎn)化成autolayout的約束饭于,如果再進(jìn)行約束的設(shè)置蜀踏,由于多次累加可能會造成沖突
github博客:https://wf96390.github.io/blog/2016/03/16/autolayout/