AutoLayout
AutoLayout是基于約束的描述性的布局系統
官方文檔:
概念
AutoLayout的核心觀念就是從基于frame的布局轉換到基于約束的布局。
基于frame的布局,frame based layout,根據相對坐標原點的位置來布局:
基于約束的布局,Constraint based layout:
約束公式
翻譯過來就是item1的某個屬性和item2的某個屬性乘以倍數加常數有某種關系
約束屬性
約束分類
按照item數目:一元約束,二元約束
按照約束屬性的作用:大小約束,位置約束(水平約束,垂直約束)
約束準則
大小屬性不能約束位置屬性。反之也是
常數值不能約束位置屬性。
非1倍數不能作用于位置屬性。
水平屬性不能約束垂直屬性,反之也是
Leading/Trailing屬性不能約束Left/Right屬性
約束關系
等于,小于或等于,大于或等于
創建約束
?xib創建:
https://github.com/cooop/iOSDemo/tree/master/AutoLayoutDemo
代碼創建:
步驟:創建一個約束,將約束添加到合適的view上,重復以上得到一個可滿足的無歧義的約束
[NSLayoutConstraint constraintWithItem:redView
????????????????????????????????????????????????????attribute:NSLayoutAttributeLeading
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?relatedBy:NSLayoutRelationEqual
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? toItem:blueView
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?attribute:NSLayoutAttributeTrailing
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? multiplier:1.0
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?constant:8];
將約束添加到合適的view上
一元約束,放在item上
二元約束,原則找兩個item的最近公共祖先
API:[view addConstraint:constraint];
[view removeConstraint:constraint];
重復以上得到一個可滿足的無歧義的約束:
AutoLayout的終極目標就是得到可滿足無歧義的布局解決方案,需要item在水平垂直兩個方向各至少需要兩個約束
不可滿足的約束,在一個方向上缺少約束,使得item在改方向上布局不能確定,會有隱式的布局問題
沖突約束,在一個方向上,item含有兩個以上約束,且約束不能同時滿足,系統會丟棄沖突約束,選擇一個滿足的解決方案,也會有隱式的布局問題
解決歧義
優先級:范圍1-1000,數值越大優先級越高,優先級高的約束率先滿足。默認約束都是Required的(最高優先級),所以不要以為優先級設為DefaultHight就很高了,還沒有不設來的高,正確的做法是把其他沖突的約束的優先級設置低。
Required = 1000
DefaultHight = 750
DefaultLow = 250
FittingSizeLevel = 50
constraint.priority = 1000
constraint.priority = UILayoutPriorityRequired
內在大小 Intrinsic Content Size
有一些view有一個內在的大小,例如UILabel設置完字體和文字,就有一個內在寬度和高度恰好包裹文字。UIImage如果有圖片,內在寬度和高度與圖片大小一致。
如果有內在寬度,在水平方向上可以少設置一個約束;如果有內在高度,在垂直方向上可以少設置一個約束。即UILabel只需要設置left和top兩個約束就可以可滿足。
view的content hugging和 compression resistance屬性
content hugging是讓view抱緊,當view的寬度約束大于view的內在寬度,content hugging優先級設置的高,view不會拉伸
compression resistance是讓view頂住,當view的寬度約束小于view的內在寬度,compression resistance優先級設置的高。view不會被壓縮
UIScrollView約束
scrollView和他外部的item之間的約束,約束的是scrollView的frame
scrollView和他內部的item之間的edges和margins約束,約束的是scrollView的contentsize
scrollView和他內部的item之間的height, width,和centers約束,約束的是scrollView的frame
Visual Format Language
VFL:描述性語言。 H:[blueView]-8-[redView]
API: [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[blueView]-8-[redView]" options:NSLayoutFormatAlignAllTop metrics:nil views:NSDictionaryOfVariableBindings(blueView,redView)]];
缺點: 太太太太容易寫錯了
AutoLayout新特性
iOS 8:
新屬性lastBaseline、firstBaseline、leftMargin、rightMargin、topMargin、bottomMargin、leadingMargin、trailingMargin、centerXMargin、centerYMargin
約束的激活的反激活:
@property (getter=isActive) BOOL active
[NSLayoutConstraint activateConstraints:constraintsArray]
[NSLayoutConstraint deactivateConstraints:constraintsArray]
iOS 9:
約束錨,NSLayoutAnchor,只需考慮約束,不用考慮加在哪個view上,類似Masonry
[imageView.trailingAnchor constraintEqualToAnchor:label.leadingAnchor constant:20]
UILayoutGuide:專門的輔助布局的控件,不會加到view上,但是可以“占位置”,省去了加一堆空白view輔助布局的煩惱
UILayoutGuide *space1 = [[UILayoutGuide alloc] init];
[self.view addLayoutGuide:space1];
UILayoutGuide *space2 = [[UILayoutGuide alloc] init];
[self.view addLayoutGuide:space2];
[space1.widthAnchor constraintEqualToAnchor:space2.widthAnchor].active = YES;
[self.saveButton.trailingAnchor constraintEqualToAnchor:space1.leadingAnchor].active = YES;
[self.cancelButton.leadingAnchor constraintEqualToAnchor:space1.trailingAnchor].active = YES;
[self.cancelButton.trailingAnchor constraintEqualToAnchor:space2.leadingAnchor].active = YES;
[self.clearButton.leadingAnchor constraintEqualToAnchor:space2.trailingAnchor].active = YES;
UIStackView:水平或垂直方向上一系列的view,以此排列,解決前一個view隱藏或刪除,后面的view需要更新約束的尷尬
Masonry
github
https://github.com/SnapKit/Masonry
官方定義:
Masonry is a light-weight layout framework which wraps AutoLayout with a nicer syntax. Masonry has its own layout DSL which provides a chainable way of describing your NSLayoutConstraints which results in layout code that is more concise and readable. Masonry supports iOS and Mac OS X
幾個點:輕量級、基于AutoLayout、鏈式布局DSL、高可讀性、支持iOS和OS X、有Swift版本SnapKithttps://github.com/SnapKit/SnapKit
NSLayoutConstraints的問題:
復雜,可讀性太低
語法
添加約束:
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
????????make.leading.equalTo(blueView.mas_trailing).multipliedBy(1).offset(8);
}];
更改約束:
mas_updateConstraints:注意只能改常數值
mas_remakeConstraints:刪除之前與view相關的所有約束重新創建
刪除約束: 需要記錄約束
@property (nonatomic, strong) MASConstraint *topConstraint;
...
// when making constraints
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
? ? ? self.topConstraint =? make.top.equalTo(superview.mas_top).with.offset(padding.top);
? ? ? make.left.equalTo(superview.mas_left).with.offset(padding.left);
}];
...
// then later you can call
[self.topConstraint uninstall];
約束
約束屬性:MASViewAttribute
約束關系:
.equalTo?equivalent to?NSLayoutRelationEqual
.lessThanOrEqualTo?equivalent to?NSLayoutRelationLessThanOrEqual
.greaterThanOrEqualTo?equivalent to?NSLayoutRelationGreaterThanOrEqual
優先級:
.priority?allows you to specify an exact priority
.priorityHigh?equivalent to?UILayoutPriorityDefaultHigh
.priorityMedium?is half way between high and low
.priorityLow?equivalent to?UILayoutPriorityDefaultLow
更靈活的語法
簡化:
make.leading.equalTo(self.view.mas_leading).multipliedBy(1).offset(0);
-----乘數是1可以省略,常數是0可以省略----->
make.leading.equalTo(self.view.mas_leading);
-----item1的屬性和item2的屬性相同,item2的屬性可以省略----->
make.leading.equalTo(self.view);
-----item1直接添加在item2上,item2可以省略, 寫@0----->
make.leading.equalTo(@(0));
-----equalTo每次常數值都要寫@,好煩,可以用mas_equalTo----->
make.leading.mas_equalTo(0);
合并
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
????????????make.top.equalTo(self.view).offset(10);????
????????????make.left.equalTo(self.view).offset(10);
????????????make.bottom.equalTo(self.view).offset(-10);
????????????make.right.equalTo(self.view).offset(-10);
}];
-----item2和乘數和常數一致,可以合并在一行寫----->
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
????????make.top.left.equalTo(self.view).offset(10);
????????make.bottom.right.equalTo(self.view).offset(-10);
}];
-----幾個屬性也可以合在一起----->
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
????????make.edges.equalTo(self.view).insets(padding);
}];
-----item1直接添加在item2上,equalTo和insets可合并----->
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
????????make.edges.mas_equalTo(padding);
}];
合并合并:
height.and.width -->size
centerX.and.centerY --> center
例如:
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
合并合并合并:
item1和item1的屬性一樣,多個item2也可以合并在一行創建多個約束,傳入數組即可
make.height.equalTo(@[view1.mas_height, view2.mas_height]);
make.height.equalTo(@[view1, view2]);
make.left.equalTo(@[view1, @100, view3.right]);
可讀性:
make.top.left.equalTo(self.view).offset(10); --> make.top.and.left.equalTo(self.view).with.offset(10);
and和with并沒有實際作用,只是單純返回self,只為了可讀性的考量
創建約束代碼的位置
參考這篇博客:
http://reviewcode.cn/article.html?reviewId=14
View中:直接在init方法里創建.
ViewController中:直接在viewDidLoad()里創建.
何時更新:需要更新的代碼如果比較少可以就在當時更新,比較多則放在updateConstraints()?,然后在合適的時候setNeedsUpdateConstraints()?批量更新
補充TableViewCell實踐經驗:創建時候在init中創建,在所有subview都加入了contentview之后,調用自定函數setupConstraints(),所有創建約束代碼加載此處,可以避免約束創建的類沒有添加到任何父類引發的崩潰。所有與model相關的約束更新放在updateConstraints()中,在bindWIthModel是調用setNeedsUpdateConstraints()?更新約束。