Masonry这个框架是使用代码进行自动布局使用的,它的使用非常广泛,这段时间一直在学习这个框架,因此想把学到的东西记下来,方便以后查阅,也便于与人分享。
自动布局约束的等式:
item1.attribute1 = multiplier × item2.attribute2 + constant
Masonry中使用了大量的点链式语法,考虑到应该有些小伙伴不知道点链式语法的来龙去脉,因此这里先整理一下点链式语法。
点链式语法
我们先来看一下Masonry框架的一种使用:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(superview.mas_left).mas_offset(30);
}];
上面的代码是Masonry的简单的使用,这里面就用到了点链式语法make.left.equalTo(superview.mas_left).mas_offset(30);
,我们看一下这句点链式语法,这里面包括三个要素:
- 点语法:我们在访问属性的时候会使用点语法。
- 小括号调用:在Objective-C中使用[ ]来调用方法,只有在调用Block的时候会使用(),因此这里我们可以使用Block来实现点链式语法中的()。
-
连续调用:Block是有返回值的,那么我们可以在每次调用完Block后返回调用者对象本身,那么我们就可以实现连续的调用了。
总结起来就是:
我们可以声明一些Block类型的属性,让block类型的属性的返回值为其本身。
下面用一个计算器的例子来说明一下:
//Calculator.h
@interface Calculator : NSObject
//这里是创建一个属性,属性的类型是block类型,属性名是add
@property (nonatomic, copy)Calculator * (^add)(NSInteger num);
@property (nonatomic, copy)Calculator * (^minus)(NSInteger num);
@property (nonatomic, copy)Calculator * (^multiply)(NSInteger num);
@property (nonatomic, copy)Calculator * (^divide)(NSInteger num);
@property (nonatomic, assign)NSInteger result;
@end
//Calculator.m
@implementation Calculator
- (instancetype)init
{
self = [super init];
if (self) {
self.result = 0;
}
return self;
}
//这里实现的是add这个属性的get方法,只不过属性的类型是block类型的。
- (Calculator * (^)(NSInteger num))add
{
return ^id(NSInteger num){
self.result += num;
return self;
};
}
- (Calculator * (^)(NSInteger num))minus
{
return ^id(NSInteger num){
self.result -= num;
return self;
};
}
- (Calculator * (^)(NSInteger num))multiply
{
return ^id(NSInteger num){
self.result *= num;
return self;
};
}
- (Calculator * (^)(NSInteger num))divide
{
return ^id(NSInteger num){
self.result /= num;
return self;
};
}
@end
调用:
Calculator *calculator = [[Calculator alloc] init];
calculator.add(5).minus(8).multiply(8).divide(23);
- 1.calculator.add是调用了add属性的get方法,这个方法会返回一个block,block如下:
return ^id(NSInteger num){
self.result += num;
return self;
};
- 2.calculator.add(5)会执行这个block,这个block的返回值是Calculator对象本身,所以calculator.add(5)执行完了得到的是一个Calculator对象。
- 3.Calculator对象继续访问minus属性,执行minus属性的get方法。
更简洁的实现
上面是通过声明一系列的block类型的属性,再实现block属性的get方法来实现链式调用,但是Masonry的实现方式和这种方式还是有区别,我们在Masonry中并没有发现Block类型的属性的声明,反而是看到了一些平时见的比较少的方法的声明:
回想一下,当我们通过点语法去访问属性的时候实质上就是访问了get方法,那么当不存在一个名为name的属性时,我们使用self.name去访问时是不是也会跑去执行名为name的方法呢?答案是肯定的,也就是只要我们申明了一个xxx方法,那就可以放心的写self.xxx。
所以最终Calculator.h文件就改成了这样:
@interface Calculator : NSObject
/*
@property (nonatomic, copy)Calculator * (^add)(NSInteger num);
@property (nonatomic, copy)Calculator * (^minus)(NSInteger num);
@property (nonatomic, copy)Calculator * (^multiply)(NSInteger num);
@property (nonatomic, copy)Calculator * (^divide)(NSInteger num);
@property (nonatomic, assign)NSInteger result;
*/
- (Calculator * (^)(NSInteger num))add;
- (Calculator * (^)(NSInteger num))minus;
- (Calculator * (^)(NSInteger num))multiply;
- (Calculator * (^)(NSInteger num))divide;
@end
Masonry的使用方法
1.使用MASConstraintMaker创建约束
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);
}];
或者更简单的方法:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
并不是只有equalTo即等于这一种关系,还可以有:
lessThanOrEqualTo:等同于NSLayoutRelationLessThanOrEqual
greaterThanOrEqualTo:等同于NSLayoutRelationGreaterThanOrEqual
2.MASViewAttribute
Masonry中有MASViewAttribute这个类,这个类就等同于NSLayoutAttribute这个类:
MASViewAttribute | NSLayoutAttribute |
---|---|
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 |
3.与常数有关的问题
自动布局不允许对齐的属性如left,right,centerY等设置为常数,如果我们传了一个常数给这些属性,Masonry会自动把这些约束变为相对于父视图的约束,即:
//creates view.left = view.superview.left + 10
make.left.equalTo(@10)
4.mas前缀相关
在使用Masonry的时候,有时候会比较迷糊什么时候使用带有mas前缀的,什么时候使用不带前缀的,我们看下面这句代码:
make.top.mas_equalTo(42);
这句代码也可以这样写:
make.top.equalTo(@42);
但是这样写就会报错:
make.top.equalTo(42);
原因就在于这个括号里面的参数类型必须是id类型,如果括号里面的参数不传id类型就传常量类型也行,那么就必须要在equalTo前面加上mas,加上mas后,mas_equalTo会把传进来的数值类型变成id类型。
5.MASCompositeConstraint类相关
Masonry给了我们几个便利的方法来让我们一次性创建多个约束,Masonry中与这个约束相关的类是MASCompositeConstraint类,简单使用如下:
edges
// make top, left, bottom, right equal view2
make.edges.equalTo(view2);
// make top = superview.top + 5, left = superview.left + 10,
// bottom = superview.bottom - 15, right = superview.right - 20
make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))
size
// make width and height greater than or equal to titleLabel
make.size.greaterThanOrEqualTo(titleLabel)
// make width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
center
// make centerX and centerY = button1
make.center.equalTo(button1)
// make centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
6.修改已经存在的约束
当我们只是修改约束的constant的时候,可以使用mas_updateConstraints
:
// this is Apple's recommended place for adding/updating constraints
// this method can get called multiple times in response to setNeedsUpdateConstraints
// which can be called by UIKit internally or in your code if you need to trigger an update to your constraints
- (void)updateConstraints {
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
当我们要修改的不止是约束的constant的时候,使用mas_updateConstraints
就力不从心了,这时就需要使用mas_remakeConstraints
:
- (void)changeButtonPosition {
[self.button mas_remakeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(self.buttonSize);
if (topLeft) {
make.top.and.left.offset(10);
} else {
make.bottom.and.right.offset(-10);
}
}];
}
解读源码
我们在解读源码的时候先从最简单最基础的使用开始,然后由浅入深,逐渐深入。下面我们先分析一下整个框架的文件结构:
下面就从一个最简单最基本的使用开始来探究源码:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(superview.mas_left).mas_offset(30);
}];
我们先不管外面的方法调用,只需要知道make是MASConstraintMaker类型的就行了,从make.left.equalTo(superview.mas_left).mas_offset(30);
开始:
- 1.
make.left
left是它的一个属性,这里调用的是属性的getter方法。
那么现在我们来总结一下make.left做了哪些事:
make.left是调用了MASConstraintMaker类的left属性的get方法,这里创建了一个MASViewAttribute对象,这个对象由一个UIView对象和一个NSLayoutAttribute来创建,这里UIView对象是view,NALayoutAttribute为NSLayoutAttributeLeft,所以这里MASViewAttribute对象也就是封装了约束等号左边的两个元素。。然后使用创建的MASViewAttribute对象来创建了一个MASViewConstraint对象,这个对象代表这一行代码所表示的整个约束。最终make.left返回了一个MASVIewConstraint对象。
需要注意的是,MASConstraintMaker
对象有一个数组类型的consrtaints
属性,新创建的MASViewConstraint对象被加入到了这个属性中,在最后添加约束的时候会遍历这个数组。
- 2.
superview.mas_left
mas_left是分类的一个属性,所以superview.mas_left会调用分类的-(MASViewAttribute *)mas_left
方法。
这里通过代码创建了一个MASViewAttribute对象,对象的view即superview,对象的attribute即NSLayoutAttributeLeft,我们看看是如何创建的:
总结一下:
superview.mas_left返回了一个MASViewAttribute对象,这个对象封装了约束等号右边的两个元素。
- 3.
make.left.equalTo(superview.mas_left)
进入equalTo查看具体实现:make.left.equalTo
会得到一个Block,那么我执行make.left.equalTo(superview.mas_left)
就是执行这个Block,即make.left.equalTo(superview.mas_left)
会执行self.equalToWithRelation(attribute, NSLayoutRelationEqual)
这一行核心代码,并返回这一行核心代码的返回值。
由于make.left是MASViewConstraint对象,所以我们要去MASViewConstraint类中查看equalWithRelation的实现:self.secondViewAttribute = attribute;
会触发secondViewAttribute
这个属性的set方法,我们看一下其set方法的实现:
make.left.equalTo()
这个括号里面传入的东西可能有三种情况,第一种是数字,第二种是一个UIView对象,如果是UIView对象,那就将其layoutAttribute设置为何firstAttribute一致,也就是我们也可以这样写:make.left.equalTo(superview)
,这样Masonry也能成功识别。第三种是传入的MASViewConstraint对象,如果传入这种对象则可以直接赋值给secondViewAttribute属性。
下面我们再来看一下第一种情况传入数字的处理方式,我们进入setLayoutConstantWithValue:
这个方法:
这样我们就清楚了
make.left.equalTo()
这个括号中传入各种不同类型的值会怎么操作。
总结一下make.left.equalTo(superview.mas_left)
做的事情:
make.left
创建了一个firstViewAttribute
,firstViewAttribute
的view属性即为
mas_makeConstraint
方法的调用者,其layoutAttribute
属性为NSLayoutAttributeLeft
,firstViewAttribute
封装了约束等式左边的两个item。接着通过传入firstViewAttribute
创建了一个MASViewConstraint
对象。superview.mas_left
则是创建了一个secondViewAttribute
对象,该对象的view即为superview,layoutAttribute
为NSLayoutAttributeLeft
。make.left.equalTo(supervie.mas_left)
则是将secondViewAttribute
赋值给MASViewConstraint
对象的secondViewAttribute
属性,并给MASViewConstraint
对象的layoutRelation
属性赋值。
- 4.
.mas_offset(30)
mas_offset
的颜色是土黄色,说明这是一个宏定义,我们点进去,发现这个宏定义是定义在MASConstraint.h
文件中:
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
这不是一个简单的宏定义,里面还进行了嵌套,我们看一下MASBoxValue()
方法做了什么,在MASUtilities.h
这个文件中找到了MASBoxValue()
这个方法:
在Masonry的用法这部分我说过mas前缀的使用,这里我们就看到了其实现方法。如果我们括号里想要直接传入数值类型而不是id类型的参数,那么前面使用的API就必须带mas前缀,否则报错,如果传入的是id类型,则前面使用的API是否带mas前缀均可。
所以.mas_offset(30)
也就等于.valueOffset(@30)
,那么我们来查看一下MASConstraint.m
中的实现:
MASViewConstraint
的layoutConstant属性:到这里
make.left.equalTo(superview.mas_left).mas_offset(30);
这行代码就全解读完了。总结一下就是:
make,left
创建了一个MASViewConstraint
对象,为这个对象的firstViewAttribute
属性赋值,superview.mas_left
即创建了一个MASViewAttribute
对象,equalTo()
即把这个MASViewAttribute
对象赋值给MASViewConstraint
对象的secondViewAttribute
属性,.mas_offset(30)
则是给MASViewConstraint
对象的layoutConstant
属性赋值为30.
- 5.
mas_makeConstraints:
mas_makeConstraints:
这个方法是在UIView的分类中定义并实现的,下面我们看一下其具体实现:
再来看[constraint install]:
,这个方法的内容比较多,我们分两部分来看:
到这里约束就添加完成了。 总结一下添加约束的大体流程:
首先根据
MASViewConstraint
对象的firstViewAttribute
和secondViewAttribute
这两个属性,访问这两个属性得到firstLayoutItem
,firstLayoutAttribute
。secondLayoutitem
,secondLayoutAttribute
。然后处理有时是对齐属性如left没有提供view的情况,这时就要设置view为其父视图。然后寻找应该将约束添加到哪个视图上,最后添加约束到对应的视图上。
组合约束(MASCompositeConstraint)
Masonry中可以直接约束size,center,edge这样的组合约束。其本质也就是把它拆成多个单个约束,比如对size的约束,就是拆成width和height这两个约束。下面我们看一下其具体的实现方法:
make.size.equalTo(superview).sizeOffset(CGSizeMake(10, 10));
- 1.
make.size
- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs
方法:
总结一下make.size
做了哪些事:
把size这一个拆分成了width和height这两个,根据这两个约束创建了两个
MASViewConstraint
对象,装到一个数组里面,使用这个数组来创建了一个MASCompositeConstraint
对象,最后返回这个MASCompositeConstraint
对象。
- 2.
.equalTo(superview)
总结一下make.size.equalTo(superview)
:
把size这一个约束拆分成了width和height这两个约束,并根据此创建了两个
MASViewConstraint
对象,根据这两个对象组成的数组去创建一个MASCompositeConstraint
对象。然后遍历MASCompositeConstraint
对象的childConstraints
数组,取出数组里面的额每个MASViewConstraint
对象,然后像处理单个MASViewConstraint
一样去处理。
- 3.
.sizeOffset(CGSizeMake(10, 10))
可以想象就是把它拆分,然后赋值给两个MASViewConstraint的layoutConstant属性,就不详细说了。
mas_updateConstraints:和mas_remakeConstraints:
自动布局约束等式:
item1.attribute1 = multiplier × item2.attribute2 + constant
有时候我们有更改约束的需求,比如我们要做一个动画,移动某个视图,那就需要改变视图约束,当我们只是改变约束的constant时,可以使用
mas_updateConstraints:
这个方法。而当我们需要改变的不止constant时,就需要调用mas_remakeConstraints:
这个方法了。
先来看一下mas_updateConstraints:
这个方法是怎么实现只改变约束的constant的:
进入
[constraintMaker install]
看看
[constraint install]
中是怎么实现的:总结起来,
mas_updateConstraints:
就是去self.installedView.constarints
这个属性数组中去遍历,看有没有这样一个NSLayoutConstraint
对象,它除了constant外,所有内容都和当前的NSLayoutConstraint
对象一致,如果有这个对象,那么就把该对象的constant改为当前NSLayoutConstraint
对象的constant。这样来完成约束条件的更新。
再来看一下mas_remakeConstraints:
当我们要改变的约束条件不止是constant这么简单时,使用mas_remakeConstraints:
就不顶用了,就要使用mas_remakeConstraints:
这个方法。
[constraintMaker install]
:这篇文章在简书的地址:https://www.jianshu.com/p/8990c5a98d29