AutoLayout & Masonry 小结

AutoLayout

AutoLayout是基于约束的描述性的布局系统

官方文档:

https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/index.html#//apple_ref/doc/uid/TP40010853-CH7-SW1

概念

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