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() 更新约束。