目录
2.1.3 install约束(执行[maker install])
Masonry是对系统的自动布局约束的一个封装。要想了解Masonry,首先,我们需要了解下系统的自动布局是怎么样的。
1 系统自带自动布局约束
1.1 有公式
iOS的系统自带的给UIView添加约束的方式:
+(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(nullable id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c
也即
view1.attr1 = view2.attr2 * multiplier + c (*是相乘的意思)
1.2 添加约束到view上的规则
【规则】:主要看view1 和view2之间到关系
(1)兄弟关系,则约束加到共同的最低父视图
(2)非兄弟关系,则沿着关系树往上找,约束加到共同的最低祖先视图
(3)直接父子关系,则约束加到父视图上
(4)都是自己,则约束加到自己身上
1.3 写一个完整的布局约束
步骤就是:
(1)创建约束(参考1.1)
(2)将约束添加到View 上(参考1.2)
// 高度约束(添加到yellowView身上)
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:50];
[yellowView addConstraint:heightConstraint];
// 间距约束(添加到self.view身上)
CGFloat margin = 20;
[self.view addConstraints:@[
// 左边
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:margin],
// 右边
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant: - margin],
// 底部
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant: - margin]
]];
2 步步深入Masonry代码
2.1 代码解析
贴一张经典的Masonry的类图。
其中,核心类:
(1)自定义的约束结构体(MASConstraint、MASViewConstraint、MASCompositeConstraint)
(2)方便用户操作的创建约束的类(MASConstraintMaker)
MASConstraintMaker 提供方便的调用方法(链式调用)让用户设置约束并生成自定义的约束结构体, MASConstraintMaker install 约束的时候解析这些自定义的约束结构体,生产系统的约束并添加到view上。
2.1.1 约束添加过程概览
首先,我们看看平常使用Masonry添加约束的方式,代码如下:
[self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.and.bottom.equalTo(self);
make.top.equalTo(self.topView.mas_bottom);
make.height.equalTo(self.topView);
}];
mas_makeConstraints方法位于文件“NSArray+MASAdditions.h”。定位到该方法如下图:
代码所做到步骤如下:
(1)设置约束的translatesAutoresizingMaskIntoConstraints为NO
translatesAutoresizingMaskIntoConstraints 为iOS前期的自动布局属性autoresizing mask,它只能设置父子视图之间的布局,不能设置兄弟视图之间的布局。
YES:自动将这些自动布局属性转换为NSLayoutConstraint对象添加到视图上。比如一些有固有的大小的视图,赋值了image的imageview,会自带有width、height的约束。
NO:不自动将~~~~~~~~~~~~~~~~~~~~~~~~~~~
【问题】:为什么要写死,设置为NO?
当translatesAutoresizingMaskIntoConstraints为YES的时候,由于会自动转换一些NSLayoutConstraint对象添加到视图,这些自动添加的NSLayoutConstraint对象有可能会跟我们设置的布局冲突。从而导致布局冲突警告。
(2)创建maker
(3)执行block,也就是
^(MASConstraintMaker *make) {
make.left.right.and.bottom.equalTo(self);
make.top.equalTo(self.topView.mas_bottom);
make.height.equalTo(self.topView);
}
执行完这些block,会创建出相应的约束结构体,这些结构体会放到maker内部的数组里保存,如下
(4)install maker的constraints数组里面的约束,也就是解析前面创建的约束结构体,生成系统的约束结构体NSLayoutConstraint,然后add到view上,完成约束的最后的添加。
2.1.2 maker添加约束的过程
从上面我们可以看到,maker添加约束的核心代码就在的MASConstraintMaker 的Block里。那么,它是如何通过这个block去创建我们要的约束的呢。下面,我们逐步进入代码进行分析。
我们就举其中的一句代码做栗子
make.left.right.and.bottom.equalTo(self);
(1) make.left
-
该约束被加入到maker的constraints数组中。
-
该约束的delegate = self(即maker)
-
make.left的结果,返回MASViewConstraint类型
-
此时maker的constraints数组为:
maker的constraints数组 |
left<delegate = maker> |
(2)make.left.right
因为(1)make.left返回MASViewConstraint类型,因此,.right的时候调用的是MASViewConstraint类型(left约束)的right函数(即基类MASConstraint的right方法)
-
将已有的left约束和新创建的right约束,放到新创建的MASCompositeConstraint里面,同时把这两个约束的delegate指向CompositeConstraint,把CompositeConstraint的delegate指向maker
-
并且把原来已经加入到maker的constraints数组的left替换成CompositeConstraint。
-
make.left.right 的结果,返回MASCompositeConstraint 类型
-
此时,maker 的constraints数组,以及该数组里保存的composite的childConstraints数组内容如下:
maker 的constraints数组 | 对应的CompositeConstraint的childConstraints数组 |
CompositeConstraint <delegate = maker> | [left, right] <delegate = CompositeConstraint> |
(3)make.left.right.and
因为(2)make.left.right返回的是MASCompositeConstraint 类型,所以调用MASCompositeConstraint 的and方法(即基类MASConstraint的and方法)
-
直接返回self,即执行的结果是返回MASCompositeConstraint类型
(4) make.left.right.and.bottom
因为(3)make.left.right.and返回的是MASCompositeConstraint类型,因此调用MASCompositeConstraint的bottom方法。
-
自从maker创建了composite类型约束,该条语句后续创建的约束都放在composite的childConstraints数组里。
-
childConstraints里的所有约束,其delegate = composite
-
maker的constraints数组里的所有约束,其delegate = maker
-
返回的最终结果是MASCompositeConstraint类型
maker 的constraints数组 | 对应的CompositeConstraint的childConstraints数组 |
CompositeConstraint <delegate = maker> | [left, right, bottom] <delegate = CompositeConstraint> |
(5)make.left.right.and.bottom.equalTo(self);
因为(4)make.left.right.and.bottom返回的类型为MASCompositeConstraint类型。因此执行MASCompositeConstraint类型的equalTo方法。(即基类的equalTo方法)
-
MASCompositeConstraint 遍历其childConstraints数组里面的约束,对每个约束(MASViewConstraint类型)调用equalToWithRelation函数
可以看到,该函数主要是给约束设置secondViewAttribute 和layoutRelation。分为两种情况。
情况一:类似make.height.equalTo(@[redView, blueView])的情况,即参考view为数组里面的所有view
-
遍历,复制自己,并分别将layoutRelation和数组里的attribute赋值进去
-
创建MASCompositeConstraint类型,将其代理设置为自己的代理(compositeConstraint)
-
将新创建的MASCompositeConstraint 替换掉自己在compositeConstraint的childConstraints数组里的位置
maker 的constraints数组 | 对应的CompositeConstraint的childConstraints数组 | childConstraints数组里的CompositeConstraint的childConstraints数组 |
CompositeConstraint <delegate = maker> | [CompositeConstraint_left, CompositeConstraint_right, CompositeConstraint_bottom] <delegate = CompositeConstraint> | [left1, left2]<delegate = CompositeConstraint_left > [right1, right]<delegate = CompositeConstraint_right> ,[bottom1, bottom2]<delegate = CompositeConstraint_bottom> |
情况二:只有一个参考view
-
仅仅将layoutRelation和attribute赋值进去
另外,MASViewConstraint类里面的secondViewAttribute的setter方法里内容很多,里面处理了多种类型的设置。
(6)一个make的约束构建完成。
(7)make.top.equalTo(self.topView.mas_bottom); 和 make.height.equalTo(self.topView)重复上面的过程
后续的每一条make语句与上述流程一样。最后,整个block执行后,maker的数组结构如下:
[self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.and.bottom.equalTo(self);
make.top.equalTo(self.topView.mas_bottom);
make.height.equalTo(self.topView);
}];
maker的constraints数组 | CompositeConstraint的childConstraints数组 | 执行的代码 |
CompositeConstraint <delegate = maker> | [left, right, bottom] <delegate = CompositeConstraint> | make.left.right.and.bottom.equalTo(self); |
top(MASViewConstraint) <delegate = maker> |
| make.top.equalTo(self.topView.mas_bottom) |
height(MASViewConstraint) <delegate = maker> |
| make.height.equalTo(self.topView) |
2.1.3 install约束(执行[maker install])
1. MASCompositeConstraint的install方法
MASCompositeConstraint的install方法,会遍历其childConstraints里面的约束结构体(一般都是MASViewConstraint),最终执行MASViewConstraint的install方法。
2. MASViewConstraint的install方法
3 总结
Masonry的思路:提供一套方便用户使用的接口(方便用户设置参照View、约束数据),将约束相关的数据封装起来,然后再解析封装好的约束对象,生成系统的NSLayoutConstraint对象,按照系统的规则添加到UIView上。
(1)通过属性和 block 的方式实现链式调用
基本思想就是通过block参数,并且返回对象本身,从而进一步点语法调用下一个属性。
Masonry 的MASConstraint这个类中的大部分方法的都返回一个block,而block的返回值都是MASConstraint,或者方法直接返回MASConstraint类型(self), 返回的MASConstraint对象又可以调用返回block的方法,正是通过这样的方式使链式语法能够工作。
(2)组合模式
首先,经典 组合模式 中的参与者:
【Client】:通过 Component 接口操纵组合部件的对象。
【Component】:为组合中的对象声明接口。在适当的情况下,实现所有类共有接口的缺省行为。声明一个接口用于访问和管理 Component 的子组件。。在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
【Leaf】:在组合中表示叶节点对象,叶节点没有子节点。在组合中定义图元对象的行为。
【Composite】:定义有子部件的那些部件的行为。在 Composite 接口中实现与子部件有关的操作。
那么,我们从 组合模式 的角度看,Masonry 框架中的角色分析:
UIView,通过分类View+MASAdditions来调用Masonry
【Client】:MASConstraintMaker
【Component】:MASConstraint
【Leaf】:MASViewConstraint
【Composite】:MASCompositeConstraint