如人饮水,冷暖自知

iOS 模块详解—「Masonry 约束」

引导


本篇文章主要从【Masonry 场景使用】学习总结,该模块学习将续更 ~
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,可留言指正或是补充,以提高文章质量@白开水ln原著;

目录:

  1. Masonry简明介绍
  2. 导入Masonry框架
  3. Masonry 常用API & 特性
    1.基础API
    2.4种设置常量的方法
    3.简化前缀的宏定义 & 简写
    4.更新约束和布局
  4. Masonry常用方法
    1.基本使用:设置内边距
    2.简单动画:priority优先级
    3.更新约束 mas_updateConstraints
    4.重写约束 mas_remakeConstraints
    5.比例使用 multipliedBy
    6.大于等于和小于等于某个值的约束
  5. Demo小样 重要的部分代码中都有相应的注解和文字打印,运行程序可以很直观的表现
  6. SourceCodeToolsClassWechatPublic-Codeidea

Masonry简明介绍


Masonry是一个轻量级的布局框架,适用于iOS以及OS X。它用简洁的语法对官方的AutoLayout进行了封装。 Masonry有它自己的一套框架用来描述NSLayoutConstraints布局的DSL,提高了约束代码的简洁性与可读性。

导入Masonry框架


  • 使用Cocoapods来导入框架,在使用到该框架的文件中添加主头文件:#import <Masonry/Masonry.h>

  • 使用直接拖拽的方式拉入框架文件夹,在使用到该框架的文件中添加主头文件:#import "Masonry.h"

Masonry 常用API & 特性


基础API
1
2
3
4
5
6
7
8
9
mas_makeConstraints() 添加约束
mas_remakeConstraints() 移除之前的约束,重新添加新的约束
mas_updateConstraints() 更新约束
equalTo() 参数是对象类型,一般是视图对象或者mas_width这样的坐标系对象
mas_equalTo() 和上面功能相同,参数可以传递基础数据类型对象,可以理解为比上面的API更强大
width() 用来表示宽度,例如代表view的宽度
mas_width() 用来获取宽度的值。和上面的区别在于,一个代表某个坐标系对象,一个用来获取坐标系对象的值

Constant:Masonry提供了4种设置constant的方法
1
2
3
4
5
6
7
- (MASConstraint * (^)(MASEdgeInsets insets))insets;
- (MASConstraint * (^)(CGSize offset))sizeOffset;
- (MASConstraint * (^)(CGPoint offset))centerOffset;
- (MASConstraint * (^)(CGFloat offset))offset;

注解:

  • insets: 用来设置left, right, top, bottom。接受MASEdgeInsets类型值
  • sizeOffset: 用来设置width, height。接受CGSize类型的值
  • centerOffset: 用来设置centerX, centerY。接受CGPoint类型的值
  • offset: 可以用来设置所有的东西。接受CGFloat类型的值

简化前缀的宏定义 & 简写
1
2
3
4
// 定义这个常量,就可以不用在开发过程中使用"mas_"前缀。
#define MAS_SHORTHAND
// 定义这个常量,就可以让Masonry帮我们自动把基础数据类型的数据,自动装箱为对象类型。
#define MAS_SHORTHAND_GLOBALS

注解:这两个宏如果想有效使用,必须要在添加Masonry头文件之前导入进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 完整的
make.left.equalTo(view1.superview.mas_left).offset(0);
//省略Attribute的
make.left.equalTo(view1.superview).offset(0);
//省略equalTo的
make.left.offset(0);
//使用equalTo替代offset的
make.left.equalTo(@0);
//省略所有的... 可惜会有warning
make.left;

更新约束和布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 设置约束
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
// 更新约束
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
// 重新设置约束
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
注解:
mas_makeConstraints: 初次设置约束使用。
mas_updateConstraints: 更新约束时使用。如果找不着这条约束,会新增,相当于mas_makeConstraints。
mas_remakeConstraints: 重新设置约束。先将view上所有约束移除,再新增约束
注意:
mas_updateConstraints只能更新已有约束。如果第一次使用的是left, right设置的相对宽度。更新的时候想换成使用width。
不能使用mas_updateConstraints,因为已有约束里面没有width的约束,新增width之后会跟原有left, right约束冲突。
此时应该使用mas_remakeConstraints
  • 关于更新约束布局相关的API,主要用以下四个API:

    • - (void)updateConstraintsIfNeeded 调用此方法,如果有标记为需要重新布局的约束,则立即进行重新布局,内部会调用updateConstraints方法。

    • - (void)updateConstraints 重写此方法,内部实现自定义布局过程。

    • - (BOOL)needsUpdateConstraints 当前是否需要重新布局,内部会判断当前有没有被标记的约束。

    • - (void)setNeedsUpdateConstraints 标记需要进行重新布局。

  • 关于UIView重新布局相关的API,主要用以下三个API:

    • - (void)setNeedsLayout 标记为需要重新布局。

    • - (void)layoutIfNeeded 查看当前视图是否被标记需要重新布局,有则在内部调用layoutSubviews方法进行重新布局。

    • - (void)layoutSubviews 重写当前方法,在内部完成重新布局操作。

1
2
3
4
5
6
7
8
9
10
11
// 可以设置任意的优先级,接受的参数是0-1000的数字
- (MASConstraint * (^)(MASLayoutPriority priority))priority;
// 设置低优先级,优先级为250
- (MASConstraint * (^)())priorityLow;
// 设置中优先级,优先级为500
- (MASConstraint * (^)())priorityMedium;
// 设置高优先级,优先级为750
- (MASConstraint * (^)())priorityHigh;

Masonry常用方法


基本使用:设置内边距

效果图往下看:创建一个View,左右上下空出10个像素

1
2
3
4
5
6
7
8
9
10
11
12
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).with.offset(150);
make.left.equalTo(self.view.mas_left).with.offset(30);
make.bottom.equalTo(self.view.mas_bottom).with.offset(30);
make.right.equalTo(self.view.mas_right).with.offset(30);
}];
//【通过insets简化设置内边距的方式】,一句代码代替上面的多行
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(150, 30, 30, 30));
}];

Simulator Screen Shot 2017年5月12日 下午7.20.03.png


简单动画:priority优先级

效果图往下看:优先级约束一般放在一个控件约束的最后面

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
- (void)testView2 {
// 红色View
UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
// 蓝色View
self.blueView = [[UIView alloc]init];
self.blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.blueView];
// 黄色View
UIView *yellowView = [[UIView alloc]init];
yellowView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:yellowView];
// ---红色View--- 添加约束
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).with.offset(20);
make.bottom.equalTo(self.view.mas_bottom).with.offset(-80);
make.height.equalTo([NSNumber numberWithInt:50]);
}];
// ---蓝色View--- 添加约束
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(redView.mas_right).with.offset(40);
make.bottom.width.height.mas_equalTo(redView);
}];
// ---黄色View--- 添加约束
[yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.blueView.mas_right).with.offset(40);
make.right.mas_equalTo(self.view.mas_right).with.offset(-20);
make.bottom.width.height.mas_equalTo(redView);
// 【优先级设置为250,最高1000(默认)】
make.left.mas_equalTo(redView.mas_right).with.offset(100).priority(250);
}];
NSLog(@"%@",redView);
}
// 点击屏幕移除蓝色View
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.blueView removeFromSuperview];
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
}];
}

注解:
这里的三个View的宽度是根据约束自动推断设置的,对黄色的View设置了一个与红色View有关的priority(250)的优先级,
它同时有对蓝色View有个最高的优先级约束(make.left.mas_equalTo(self.blueView.mas_right).with.offset(40);)
当点击屏幕是,我将蓝色View移除,此时第二优先级就是生效。

priority优先级.gif


更新约束 mas_updateConstraints

效果图往下看:创建一个按钮,约束好它的位置(居中,宽高等于100且小于屏幕宽高值)。
每次点击一次这个按钮,其宽高将增大一定的倍数,最终其宽高等于屏幕宽高时将不再变化。

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
- (void)testView3 {
self.growingButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self setWithButton:self.growingButton title:@"mas_updateConstraints更新约束-点我放大"];
[self.growingButton addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.growingButton];
self.scacle = 1.0;
[self.growingButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.view);
// 初始宽、高为100,优先级最低
make.width.height.mas_equalTo(100 * self.scacle);
// 最大放大到整个view +
make.width.height.lessThanOrEqualTo(self.view);
}];
}
- (void)buttonClick {
self.scacle += 1.0;
// 告诉self.view约束需要更新
[self.view setNeedsUpdateConstraints];
// 调用此方法告诉self.view检测是否需要更新约束,若需要则更新,下面添加动画效果才起作用
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}
#pragma mark - updateViewConstraints
// 重写该方法来更新约束
- (void)updateViewConstraints {
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
// 这里写需要更新的约束,不用更新的约束将继续存在
// 不会被取代,如:其宽高小于屏幕宽高不需要重新再约束
make.width.height.mas_equalTo(100 * self.scacle);
}];
[super updateViewConstraints];
}

更新约束 mas_updateConstraints.gif


重写约束 mas_remakeConstraints

创建一个按钮,约束好其位置(与屏幕上左右的距离为0,与屏幕底部距离为350),点击按钮后全屏展现(即与屏幕底部距离为0)。

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
- (void)testView4 {
self.isExpanded = NO;
self.growingButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self setWithButton:self.growingButton title:@"mas_remakeConstraints重写约束-点我展开"];
[self.growingButton addTarget:self action:@selector(testView4BtnClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.growingButton];
[self.growingButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.right.mas_equalTo(0);
make.bottom.mas_equalTo(-350);
}];
}
- (void)testView4BtnClick {
self.isExpanded = !self.isExpanded;
if (!self.isExpanded) {
[self.growingButton setTitle:@"mas_remakeConstraints重写约束-点我展开" forState:UIControlStateNormal];
} else {
[self.growingButton setTitle:@"mas_remakeConstraints重写约束-点我收起" forState:UIControlStateNormal];
}
// 告诉self.view约束需要更新
[self.view setNeedsUpdateConstraints];
// 调用此方法告诉self.view检测是否需要更新约束,若需要则更新,下面添加动画效果才起作用
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}
#pragma mark - updateViewConstraints
- (void)updateViewConstraints {
// 这里使用update也能实现效果
// remake会将之前的全部移除,然后重新添加
__weak __typeof(self) weakSelf = self;
[self.growingButton mas_remakeConstraints:^(MASConstraintMaker *make) {
// 这里重写全部约束,之前的约束都将失效
make.top.mas_equalTo(0);
make.left.right.mas_equalTo(0);
if (weakSelf.isExpanded) {
make.bottom.mas_equalTo(0);
} else {
make.bottom.mas_equalTo(-350);
}
}];
[super updateViewConstraints];
}

注解:
mas_remakeConstraintsmas_updateConstraints 的区别在于
前者重新对视图进行了约束(抛弃了之前的约束),后者是更新约束条件(保留未更新的约束),
如:这次更新了对 height 的约束,其他对X&Y以及宽的约束不变)。

重写约束 mas_remakeConstraints.gif


比例使用 multipliedBy

使用multipliedBy必须是对同一个控件本身,如果修改成相对于其它控件会出导致Crash

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
- (void)testView5 {
UIView *topView = [[UIView alloc]init];
[topView setBackgroundColor:[UIColor redColor]];
[self.view addSubview:topView];
UIView *topInnerView = [[UIView alloc]init];
[topInnerView setBackgroundColor:[UIColor greenColor]];
[topView addSubview:topInnerView];
UIView *bottomView = [[UIView alloc]init];
[bottomView setBackgroundColor:[UIColor blueColor]];
[self.view addSubview:bottomView];
UIView *bottomInnerView = [[UIView alloc]init];
[bottomInnerView setBackgroundColor:[UIColor blackColor]];
[bottomView addSubview:bottomInnerView];
[topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_equalTo(0);
make.height.mas_equalTo(bottomView);
}];
[topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
// 求解❓
make.width.mas_equalTo(topInnerView.mas_height).multipliedBy(2);
make.center.mas_equalTo(topView);
}];
[bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.bottom.right.mas_equalTo(0);
make.height.mas_equalTo(topView);
make.top.mas_equalTo(topView.mas_bottom);
}];
[bottomInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.mas_equalTo(bottomView);
make.height.mas_equalTo(bottomInnerView.mas_width).multipliedBy(4);
make.center.mas_equalTo(bottomView);
}];
// NSLog(@"%@",NSStringFromCGRect(topInnerView.bounds));
}
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,以提高文章质量@jianshu - 白开水ln。


大于等于和小于等于某个值的约束

UILabel 包裹约束

参考:http://www.jianshu.com/p/99c418cd11f7

Reading


  • 如果在阅读过程中遇到 error || new ideas,希望你能 issue 我,我会及时补充谢谢。

  • 不管谁的博客上面写的 (也包括自己),阅读的你要敢于去验证;如人饮水,冷暖自知;(共勉)。

  • 喜欢可 赞赏 or Star一波;点击左上角关注 或 微众『Codeidea』,在 Demo 更新时收到邮件通知,便捷你的阅读。

🖋 Plain boiled water ln : 本文结束    感谢阅读 ^_^. Need coffee?
👁 At this time suggest 1 minute eye exercises

本文标题:iOS 模块详解—「Masonry 约束」

文章作者:白开水ln

原始链接:http://plainboiledwaterln.cn/iOSNET/Masonry.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际 『微众圈:Codeidea』本博客文章除特别声明外均为原创,如需转载请务必保留原链接(可点击进入的链接)和作者出处,谢谢合作!

「喜欢就留言or赞赏」but「支持不要超过你早餐费的 0.5」 ^_^.