让Masonry来拯救你的AutoLayout

前言

iPhone6 屏幕尺寸出现之后,大部分开发者都也开始使用起 Autolayout 来进行UI的布局了。相比之前的 autoresizingMaskAutolayout 的有点不言而喻。之前自己一直用 storyboard 或者是 xib 来进行开发。感觉 Autolayout 就是几个约束,控件的拖拖拉拉,有趣也简单。直到最近。。。

当你开始进行多人项目合作,用拖拽来实现 Autolayout 的弊端就显现了,在两个人同时对一个 storyboard 文件进行修改,在 svn 上 进行文件 merge 时,文件往往是会有冲突的。而且有时冲突报错还是比较能以解决的。所以多人项目里一般会用纯代码来实现UI布局。于是与,我向我的实习导师借来了下面这本书:

准备潜心修炼!

但是现实总是比较骨感的~这语法又臭又长~我觉得上面这本书应该叫《Autolayout从入门到放弃》。

有种小学看红楼梦的感觉。但是“红楼梦”是好东西啊,就像 Autolayout 一样。事情总是有解决方法的:对于新手来说,入门 Autolayout 的捷径就是 Masonry

什么是 Masonry

Masonry 是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局 简洁明了,并具有高可读性 而且同时支持 iOS 和 Max OS X。

如果我们使用原生的 Autolayout 代码,可能会有下面这一坨:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
UIView *superview = self.view;

UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[superview addConstraints:@[

//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],

]];

但是如果你使用 Masory,那么你只需要这样:

1
2
3
4
5
6
7
8
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

还能这样:

1
2
3
4
5
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];

我就问你,这么简单的语法,明明白白的语义,你入不入坑?

Masory 简单原理

Masory 源码

Masory 的核心还是 Autolayout,通过编写 UIView 的分类(category)— View+MASAdditions。为我们正常使用的 UIView 添加了新的API。再使用 block 来进行布局定义,交付类 MASConstraintMaker 来进行管理。

说白了,就是把 Autolayout 进行封装和人性化。定义最最“弱智”的接口给我们用。

相当于把红楼梦拍成动画片。哈哈~

具体的实现还是要看源码啊~

Masory 的使用

Masory 与 Autolayout 的对应关系

Masonry NSAutoLayout
view.mas_left NSLayoutAttributeLeft
view.mas_right NSLayoutAttributeRight
view.mas_top NSLayoutAttributeTop
view.mas_bottom NSLayoutAttributeBottom
view.mas_leading NSLayoutAttributeLeading
view.mas_trailing NSLayoutAttributeTrailing
view.mas_width NSLayoutAttributeWidth
view.mas_height NSLayoutAttributeHeight
view.mas_centerX NSLayoutAttributeCenterX
view.mas_centerY NSLayoutAttributeCenterY
view.mas_baseline NSLayoutAttributeBaseline

基础 – 居中显示一个view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UIView *greenView = UIView.new;
greenView.backgroundColor = UIColor.greenColor;
greenView.layer.borderColor = UIColor.blackColor.CGColor;
greenView.layer.borderWidth = 2;
[self addSubview:greenView];

UIView *superview = self;

[greenView makeConstraints:^(MASConstraintMaker *make) {
//确定view的中心点与父级的view中心点相同
make.center.equalTo(superview);
//确定view的宽和高
make.size.mas_equalTo(CGSizeMake(300, 300));

}];

代码效果:

补充:

在使用 masonry 的项目里面我们会看到 mas_equalToequalTo 是混合使用的,其实,二者还是有一定的区别的:

其实 mas_equalTo是一个MACRO.

1
2
3
4
5
#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))

#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))

可以看到 mas_equalTo 只是对其参数进行了一个BOX操作(装箱) MASBoxValue 的定义具体可以看看源代码.

所支持的类型 除了 NSNumber支持的那些数值类型之外 就只支持CGPoint CGSize UIEdgeInsets.

初级 – 让一个view略小于其superView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
UIView *greenView = UIView.new;
greenView.backgroundColor = UIColor.greenColor;
greenView.layer.borderColor = UIColor.blackColor.CGColor;
greenView.layer.borderWidth = 2;
[self addSubview:greenView];

UIView *redView = UIView.new;
redView.backgroundColor = UIColor.redColor;
redView.layer.borderColor = UIColor.blackColor.CGColor;
redView.layer.borderWidth = 2;
[greenView addSubview:redView];

UIView *superview = self;
CGFloat padding = 10.f;

[greenView makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(superview);
make.size.mas_equalTo(CGSizeMake(300, 300));

}];

[redView mas_makeConstraints:^(MASConstraintMaker *make) {

make.top.equalTo(greenView).with.offset(padding);
make.left.equalTo(greenView).with.offset(padding);
make.bottom.equalTo(greenView).with.offset(-padding);
make.right.equalTo(greenView).with.offset(-padding);

/* 也等价于 make.top.left.bottom.and.right.equalTo(greenView).with.insets(UIEdgeInsetsMake(padding, padding, padding, padding));
*/

}];

代码效果:

补充:

top,left,bottom,righ计算的是绝对的数值,计算的bottom 需要小于sv的底部高度 所以要-10 同理用于right。

初级 – 让两个高度一定的view垂直居中且等宽且等间隔排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
UIView *greenView = UIView.new;
greenView.backgroundColor = UIColor.greenColor;
greenView.layer.borderColor = UIColor.blackColor.CGColor;
greenView.layer.borderWidth = 2;
[self addSubview:greenView];

UIView *redView1 = UIView.new;
redView1.backgroundColor = UIColor.redColor;
redView1.layer.borderColor = UIColor.blackColor.CGColor;
redView1.layer.borderWidth = 2;
[greenView addSubview:redView1];

UIView *redView2 = UIView.new;
redView2.backgroundColor = UIColor.redColor;
redView2.layer.borderColor = UIColor.blackColor.CGColor;
redView2.layer.borderWidth = 2;
[greenView addSubview:redView2];

UIView *superview = self;
CGFloat padding = 10.f;

[greenView makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(superview);
make.size.mas_equalTo(CGSizeMake(300, 300));

}];

[redView1 mas_makeConstraints:^(MASConstraintMaker *make) {

make.centerY.mas_equalTo(greenView.mas_centerY);
make.left.equalTo(greenView.mas_left).with.offset(padding);
make.right.equalTo(redView2.mas_left).with.offset(-padding);
make.width.equalTo(redView2.width);
make.height.mas_equalTo(@150);
}];

[redView2 mas_makeConstraints:^(MASConstraintMaker *make) {

make.centerY.mas_equalTo(greenView.mas_centerY);
make.right.equalTo(greenView.mas_right).with.offset(-padding);
make.left.equalTo(redView1.mas_right).with.offset(padding);
make.width.equalTo(redView1.width);
make.height.mas_equalTo(@150);

}];

代码效果:

中级 – 在UIScrollView顺序排列一些view并自动计算contentSize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
- (id)init {
self = [super init];
if (!self) return nil;

UIScrollView *scrollView = UIScrollView.new;
self.scrollView = scrollView;
scrollView.backgroundColor = [UIColor grayColor];
[self addSubview:scrollView];
[self.scrollView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];

[self generateContent];

return self;
}

- (void)generateContent {
UIView* contentView = UIView.new;
[self.scrollView addSubview:contentView];

[contentView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.scrollView);
make.width.equalTo(self.scrollView);
}];

UIView *lastView;
CGFloat height = 25;

for (int i = 0; i < 10; i++) {
UIView *view = UIView.new;
view.backgroundColor = [self randomColor];
[contentView addSubview:view];

[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(lastView ? lastView.bottom : @0);
make.left.equalTo(@0);
make.width.equalTo(contentView.width);
make.height.equalTo(@(height));
}];

height += 25;
lastView = view;
}

[contentView makeConstraints:^( MASConstraintMaker *make) {
make.bottom.equalTo(lastView.bottom);
}];
}

- (UIColor *)randomColor {
CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0
CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white
CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black
return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1];
}

高级 – 根据屏幕宽高等宽等高等距离生成view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
- (instancetype)init {
self = [super init];
if (!self) return nil;

NSInteger count1 = 5;//设置一排view的个数
NSInteger count2 = 5; //设置一列view的个数
NSInteger margin = 5;//设置相隔距离

UIView * tempView1 = [[UIView alloc]init];
for (int k = 0; k < count1; k++)
{
UIView * fatherView = [[UIView alloc]init];
fatherView.backgroundColor = [UIColor clearColor];
[self addSubview:fatherView];

if (k == 0) {
[fatherView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self);
make.top.equalTo(self).offset(margin);
make.right.equalTo(self);
}];
}
else if (k == count2 - 1){
[fatherView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self);
make.left.equalTo(self);
make.top.equalTo(tempView1.mas_bottom).offset(margin);
make.height.equalTo(tempView1);
make.bottom.equalTo(self).offset(-margin);
}];
}
else{
[fatherView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self);
make.top.equalTo(tempView1.mas_bottom).offset(margin);
make.right.equalTo(self);
make.height.equalTo(tempView1);
}];
}

tempView1 = fatherView;

UIView * tempView2 = [[UIView alloc]init];
for (int i = 0; i < count2; i ++) {
UIView * childrenView = [[UIView alloc]init];
childrenView.backgroundColor = [UIColor greenColor];
[self addSubview:childrenView];

if (i == 0) {
[childrenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(fatherView).offset(margin);
make.top.equalTo(fatherView);
make.bottom.equalTo(fatherView);
}];
}
else if (i == count2 - 1){
[childrenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(fatherView).offset(-margin);
make.left.equalTo(tempView2.mas_right).offset(margin);
make.width.equalTo(tempView2);
make.top.equalTo(fatherView);
make.bottom.equalTo(fatherView);
}];
}
else{
[childrenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(tempView2.mas_right).offset(margin);
make.top.equalTo(fatherView);
make.bottom.equalTo(fatherView);
make.width.equalTo(tempView2);
}];
}

tempView2 = childrenView;
}
}

return self;
}

代码效果:

竖屏:

横屏:

最后的话

masonry 真的为 autolayout 带来了许许多多的方便之处,更深的使用和研究还是要大家自己去探索!