一、安装Masonry框架
第一步:创建一个工程‘MasonryDemo’
第二步:在项目文件下,创建一个PodFile文件
touch PodFile
第三步:用vim命令打开PodFile
vim PodFile第四步:输入以下内容
platform :ios, '14.4' #系统编号,自己更改
target 'MasonryDemo' do #'MasonryDemo'工程名字,自己根据自己的工程名字更改
pod 'Masonry'
end
第五步:输入pod install命令,运行结果如下

运行成功会出现一个MasonryDemo.xcworkspace文件,我们打开MasonryDemo.xcworkspace文件进行编写代码运行即可。

二、Masonry基础
2.1 Masonry基础API
mas_makeConstraints() 添加约束
mas_remakeConstraints() 移除之前的约束,重新添加新的约束
mas_updateConstraints() 更新约束,写哪条更新哪条,其他约束不变
equalTo() 参数是对象类型,一般是视图对象或者mas_width这样的坐标系对象
mas_equalTo() 和上面功能相同,参数可以传递基础数据类型对象,可以理解为比上面的API更强大[vi mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(200, 200));
make.center.equalTo(self.view);
}];2.2 设置内边距
yellow视图与vi视图大小等大,并且有一些内边距,可以自己自行设计,代码如下:
[self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(vi).with.offset(10);
.equalTo(vi).with.offset(40);
make.right.equalTo(vi).with.offset(-10);
make.bottom.equalTo(vi).with.offset(-10);
}];另一种写法:
[self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
// 下、右不需要写负号,insets方法中已经为我们做了取反的操作了。
make.edges.equalTo(vi).insets(UIEdgeInsetsMake(40, 10, 10, 10));
}];结果如下:

2.3 大于等于和小于等于某个值的约束
[self.textLable mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
// 设置宽度小于等于200
make.width.lessThanOrEqualTo(@200);
// 设置高度大于等于10
make.height.greaterThanOrEqualTo(@10);
}];
self.textLable.text = @"这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。这是测试的字符串。";
self.textLable.numberOfLines = 0; //可以自己试验一下这行去掉了是什么效果,和更改数字的效果
参考文档:
三、VFL布局
3.1 VFL的思想
- VFL的思想与其他的实现方法有所不同,它更为宏观化,它将约束分成了两块
- 水平方向(H:)
- 垂直方向(V:)
- 也就是说,大家在创建约束的时候,得把水平与垂直方向的约束用字符串一并表达出来,而不是一个一个的添加
3.2 代码解析
首先我们需要创建两个view,一个红view,一个蓝view;这两个view的中间间距是20,距离左边距,下边距,右边距距离也是30,代码如下:
UIView *redView = [[UIView alloc] init];
UIView *blueView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
blueView.backgroundColor = [UIColor blueColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
blueView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];
[self.view addSubview:blueView];
NSDictionary *views = NSDictionaryOfVariableBindings(redView,blueView);
NSDictionary *spaceMetrics = @{@"space": @30,
@"height" : @100};
//水平
NSString *hRedBlueVFL = \
@"H:|-space-[blueView]-space-[redView(==blueView)]-space-|";
NSArray *hLaoutConstraints = \
[NSLayoutConstraint constraintsWithVisualFormat:hRedBlueVFL
options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom
metrics:spaceMetrics
views:views];
[self.view addConstraints:hLaoutConstraints];
//竖直
NSString *vRedBlueVFL = @"V:[blueView(50)]-space-|";
NSArray *vLayoutConstraints = \
[NSLayoutConstraint constraintsWithVisualFormat:vRedBlueVFL
options:kNilOptions
metrics:spaceMetrics
views:views];
[self.view addConstraints:vLayoutConstraints];结果如下:

好的,让我们现在来分析一下代码:
- 首先我们来介绍一下API
/**
* VFL创建约束的API
*
* @param format 传入某种格式构成的字符串,用以表达想要添加的约束,如@"H:|-margin-[redView(50)]",水平方向上,redView与父控件左边缘保持“margin”间距,redView的宽为50
* @param opts 对齐方式,是个枚举值
* @param metrics 一般传入以间距为KEY的字典,如: @{ @"margin":@20},KEY要与format参数里所填写的“margin”相同
* @param views 传入约束中提到的View,也是要传入字典,但是KEY一定要和format参数里所填写的View名字相同,如:上面填的是redView,所以KEY是@“redView”
*
* @return 返回约束的数组
*/
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;
//部分NSLayoutFormatOptions的枚举选项
/*
NSLayoutFormatAlignAllLeft = (1 << NSLayoutAttributeLeft),//左边缘对齐
NSLayoutFormatAlignAllRight = (1 << NSLayoutAttributeRight),//右边缘对齐
NSLayoutFormatAlignAllTop = (1 << NSLayoutAttributeTop),
NSLayoutFormatAlignAllBottom = (1 << NSLayoutAttributeBottom),
NSLayoutFormatAlignAllLeading = (1 << NSLayoutAttributeLeading),//左边缘对齐
NSLayoutFormatAlignAllTrailing = (1 << NSLayoutAttributeTrailing),//右边缘对齐
NSLayoutFormatAlignAllCenterX = (1 << NSLayoutAttributeCenterX),//垂直方向中心对齐
NSLayoutFormatAlignAllCenterY = (1 << NSLayoutAttributeCenterY),//水平方向中心对齐
*/- 我们需要一下属性translatesAutoresizingMaskIntoConstraints,该属性是将自动布局更改为Constraints布局,该属性默认值是YES,我们需要手动置为NO
- 接下我们需要连接一下那两行字符串的意思
//水平
NSString *hRedBlueVFL = \
@"H:|-space-[blueView]-space-[redView(==blueView)]-space-|";
//竖直
NSString *vRedBlueVFL = @"V:[blueView(50)]-space-|";- ”H“代表的是水平的样式 ”V“代表的是竖直的样式
- space表示的是距离,也可以直接用具体的数字代替,也可以用其他的字符串代替,这都是可以自己定义的,定义的数据可以存到字典里面,传达API里的metrics就可以了
- [redView(==blueView)] 这句话代表的意思是redView的size跟blueView的size是一样的
- 在”H“的字符串里面写blueView(50):代表的是 blueView的宽是50;在”V“的字符串里面写blueView(50):代表的是blueView的高的50
- ”|“ 这个代表的是边界
我们这会同意翻译一下这两句话:
- @"H:|-space-[blueView]-space-[redView(==blueView)]-space-|"
blueView的左边距的大小是space,redView的右边距的大小是space,blueView与redView的大小相等,并且二者之间的距离为space
- @"V:[blueView(50)]-space-|"
blueView的高的50,并且距离下边距的距离为space
四、使用Masonry实现动画
点击按钮,视图View可以上下移动,代码如下:
- (void)moveView {
[self.view setNeedsUpdateConstraints];
[self.view updateConstraintsIfNeeded];
if (self.count%2==0) {
[UIView animateWithDuration:5 animations:^{
[self.animaView mas_updateConstraints:^(MASConstraintMaker *make) {
.equalTo(self.view).offset(700);
}];
[self.view layoutIfNeeded];
}];
}
else {
[UIView animateWithDuration:5 animations:^{
[self.animaView mas_updateConstraints:^(MASConstraintMaker *make) {
.equalTo(self.view).offset(418);
}];
[self.view layoutIfNeeded];
}];
}
self.count++;
}想要更新约束时添加动画,就需要调用关键的一行代码:setNeedsUpdateConstraints,这是选择对应的视图中的约束需要更新。
对于updateConstraintsIfNeeded这个方法并不是必须的,但是有时候不调用就无法起到我们的效果。但是,官方都是这么写的,从约束的更新原理上讲,这应该写上。我们要使约束立即生效,就必须调用layoutIfNeeded此方法。
问题:为什么要加layoutIfNeeded方法?
我们把[self.view layoutIfNeeded];这代代码注释掉,将self.animaView的信息进行打印,结果如下。

我们发现打印出来的第一条数据,仍是初始化的数据,因为在使用Masonry的时候,默认情况下,设置的约束并不会立即生效。接下来我们将注释解开,结果如下:

我们发现打印出来的第一条数据,已经是我们动画之后的数据了,所以layoutIfNeeded的作用就是使设置控件的约束立即生效。
五、源码解析
5.1 类的作用
类图:

5.1.1 View+MASAdditions类的介绍
该类主要包含两个部分,一部分是View的扩展属性,另一部分是View的扩展方法。后面会介绍扩展方法的逻辑。
5.1.2 MASViewAttribute类的介绍
该类主要包含了三个属性,三个方法。从名字我们大致能猜到,该类是对UIView与NSLayoutAttribute的封装。用一个等式来表示就是:MASViewAttribute = UIView + NSLayoutAttribute + item。在MASViewAttribute中view表示约束的对象,item表示该对象被约束的部分。对于UIView来说该item就是UIView本身。该类中除了两个构造器外还有一个isSizeAttribute方法,该方法用来判断MASViewAttribute类中的layoutAttribute属性是否是NSLayoutAttributeWidth或者NSLayoutAttributeHeight,如果是Width或者Height的话,那么约束就添加到当前View上,而不是添加在父视图上。
5.1.3 MASViewConstraint类的介绍
该类是对NSLayoutConstriant类的进一步封装。MASViewConstraint做的最核心的一件事情就是初始化NSLayoutConstriant对象,并将该对象添加在相应的视图上。因为NSLayoutConstriant在初始化时需要NSLayoutAttribute和所约束的View,而MASViewAttribute正是对View与NSLayoutAttribute进行的封装,所以MASViewConstraint类要依赖于MASViewAttribute类。如上图所示。
5.1.4 MASConstraintMaker类的介绍
该类就是一个工厂类,负责创建MASConstraint类型的对象(依赖于MASConstraint接口,而不依赖于具体实现)。mas_makeConstraints方法中的Block的参数就是MASConstraintMaker的对象。用户可以通过该Block回调过来的MASConstraintMaker对象给View指定要添加的约束以及该约束的值。该工厂中的constraints属性数组就记录了该工厂创建的所有MASConstraint对象。
5.2 点语法
.equalTo(vi).with.offset(40);在使用Masonry框架,我们会使用很多这种调用的方法,他是如何实现的呢?
在MASConstraint中的top,button等约束的getter的方法都会调用下面的这个方法,而这个方法所做的事情就是用过delegate调用MASConstraintMaker中的constaint这个方法,来根据layoutAttribute创建对应的MASConstraint对象。

我们以.left.right为例。此处的maker, 就是我们的MASConstraintMaker工厂对象,会返回带有NSLayoutAttributeTop属性的MASViewConstraint类的对象,我们先做一个转换:newConstraint = 。那么.left 等价于newConstraint.left,需要注意的是此刻调用的left方法就不在是我们工厂MASConstraintMaker中的left的getter方法了,而是被换到MASViewConstraint类中的left属性的getter方法了。给newConstraint设置代理就是为了可以在MASViewConstraint类中通过代理来调用MASConstraintMaker工厂类的工厂方法来完成创建。

而想offset(40)这种调用方式是如何实现的呢?
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}通过代码,我们发现offset函数的返回值是一个匿名block,这匿名的block有个CGFloat参数,返回值是MASConstraint对象。
总结一下: 会通过delegate的方式创建一个MASConstraint对象,该对象会继续访问equalTo属性,执行equalTo的get方法,会执行get方法里面的block,block的返回值是MASConstraint对象,该对象就会继续通过以上两种方式进行执行。
5.3 对约束方法的源码解析
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.updateExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.removeExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}首先我们来看一下mas_makeConstraints方法。
我们通过使用mas_makeConstraints对当前视图进行添加的约束,mas_makeConstraints的返回值是一个数组,该数组存放的是对当前视图添加的所有约束。因为MASViewConstraint是对NSLayoutConstraint的封装,所以该数组存储的是MASViewConstraint对象。
该函数的参数是一个block,该block的作用就是用过block给MASConstraintMaker对象中的MAConstraint属性进行初始化。
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
//关闭自动添加约束,这块是需要手动添加的,默认是YES
self.translatesAutoresizingMaskIntoConstraints = NO;
//初始化MASConstraintMaker
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
//给maker中的各种属性赋值,通过block进行值的回调
block(constraintMaker);
//进行约束的添加,并返回install的约束数组(Array<MAConstraint>)
return [constraintMaker install];
}更新与删除的原理是一样的,就是这两个函数都多了两个属性。mas_updateConstraints中将constraintMaker中的updateExisting设置为YES, 也就是说当添加约束时要先检查约束是否已经被安装了,如果被添加了就更新,如果没有被添加就新添加。而mas_remakeConstraints中所做的事情是将removeExisting属性设置成YES, 表示将当前视图上的旧约束进行移除,然后添加上新的约束。
5.4 工厂类中的install
该工厂类不仅仅创建MASConstraint对象,还负责调用MASConstraint对象的install方法来将相应的约束安装在想要的视图上面。该工厂类中的install函数就是遍历工厂对象所创建所有约束对象并调用每个约束对象的install方法来进行约束的安装。

5.5 MASViewConstraint中install
MASViewConstraint中install方法负责创建MASLayoutConstraint对象,并且将该对象添加到相应的View上。
- (void)install {
//如果已经添加过约束,就return
if (self.hasBeenInstalled) {
return;
}
//如果layoutConstraint可以使用“isActive”方法,并且self.layoutConstraint不为nil
if ([self supportsActiveProperty] && self.layoutConstraint) {
//激活约束
self.layoutConstraint.active = YES;
//将已激活的约束添加到当前View的已安装约束的数组中
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
#pragma -- Mark 使用NSLayoutConstraint添加约束
//创建约束
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
//寻找约束添加的View
if (self.secondViewAttribute.view) {
//寻找两个视图的公共父视图
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view
mas_closestCommonSuperview:self.secondViewAttribute.view];
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
self.installedView = self.firstViewAttribute.view;
} else {
self.installedView = self.firstViewAttribute.view.superview;
}
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
//更新约束
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
//添加约束
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self]; //约束所在的View增加被添加的约束
}
}参考文档
iOS开发之Masonry框架源码解析
















