Masonry 是 iOS 中的一套布局框架,先占个坑学习下源码。
使用
添加约束
1 | UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10); |
添加约束必须先把视图添加到父视图上,否则会crash
约束添加几倍于
1 | make.width.equalTo(superview.mas_width).multiplieBy(0.5); |
约束不超过或者不小于
1 | // 不小于 |
优先级
可以设置三种优先级 .priorityHigh
,.priorityMedium
,.priorityLow
。也可以自己设置优先级的大小。默认的优先级为 1000
1 | make.left.greaterThanOrEqualTo(label.mas_left).with.priorityLow(); |
设置边距大小和中心点
edgs
1 | // make top, left, bottom, right equal view2 |
size
1 | // make width and height greater than or equal to titleLabel |
center
1 | // make centerX and centerY = button1 |
更新约束
使用 mas_updateConstraints
更新约束
1 | [self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) { |
重新设置约束
使用 mas_remakeConstraints
重新设置约束
1 | [self.button mas_remakeConstraints:^(MASConstraintMaker *make) { |
多个控件等距、等宽排列
Masonry 提供了两个方法,可以提供等距排列或者等宽排列:
1 | /** |
要注意的是,如果是横排,那么还是需要自己设置高度约束;如果是竖排,还是需要设置宽度约束。例:
1 | // 导入 NSArray 的分类 |
这个方法在官方的 README 中没有看到,只是在阅读源码的时候才注意到
源码解析
文件结构
使用 Masonry 设置约束的时候,主要的几个类如上图所示。最上面的三个是调用者,分别在 UIView
,UIViewController
和 NSArray
中添加分类方法。下面的 MASConstraintMaker
是约束的创建者,我们在 block 中使用的 make
就是这个类的实例。后面的 MASContraint
和 MASViewAdditions
不对使用者暴露,前者是约束实例的封装,后者是所约束对象的封装。
我们使用的时候主要调用 View+MASAdditions
中的方法,直接在某个 View 上添加约束。 NSArray+MASAdditions
可以将多个视图放在一个数组中,然后对其中的每一个视图加上约束,用的很少。ViewController+MASAdditions
主要操作的是topLayoutGuide
和 bottomLayoutGuide
。这两个 View 的属性在 iOS11 中已经被废弃,建议使用 View 中的 mas_safeAreaLayoutGuide
替代。因此,几乎不使用
View+MASAdditions
这个 UIView 的分类提供了三个方法分别用来创建、更新、重设约束。先来看创建约束的方法:
1 | - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { |
这个方法中首先把 translatesAutoresizingMaskIntoConstraints
设置为 NO,表示要自己添加约束。然后初始化 MASConstraintMaker
。接着把 MASConstraintMaker
作为参数传入 block 执行,配置约束。最后调用install
方法,把配置好的越是添加到视图上。
这里外部传来的 block 不会被保存,而是直接执行。这就保证了 block 中直接强引用 self 也不会产生循环引用。
再看更新和重设方法:
1 | - (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block { |
和创建方法类似,只是多加了两个标记 updateExisting
,removeExisting
,用来和创建区别开。
MASConstraintMaker
我们在设置约束的时候一般是这么写的:
1 | [view1 mas_makeConstraints:^(MASConstraintMaker *make) { |
我们来看看这个 top
方法如何实现的:
1 | - (MASConstraint *)top { |
中间省略了一些暂时不涉及的代码。这里先是初始化了 MASViewAttribute
对象。由于 MASViewAttribute
只是简单的约束作用的视图以及一个枚举属性 NSLayoutAttribute
(NSLayoutAttribut
就是在创建约束的时候标识约束的类型的,比如宽度,高度,上下左右等)的封装,比较简单,这里就直接看一些它的初始化方法:
1 | - (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute { |
除了保存 NSLayoutAttribute
就是对 view 做了一个弱引用的保存。可以看到,除了 view
外,还有一个成员变量 item
。一般情况下,view
和 item
是同一个东西。只有当添加的约束是 ViewController 的 topLayoutGuide
和 bottomLayoutGuide
时才会不同:
1 | - (MASViewAttribute *)mas_topLayoutGuide { |
MASViewAttribute
的初始化方法之后,又初始化了 MASViewConstraint
对象,并把它加入到 MASConstraintMaker
的 constraints
约束数组中,并最终返回。
MASViewConstraint
1 | make.top.equalTo(superview.mas_top).with.offset(20); |
make.top
返回的是 MASViewConstraint
对象实例,所以后面的 equalTo
方法就是 MASViewConstraint
的方法。
先来看它的初始化方法:
1 | - (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute { |
从名字就可以看出,初始化的时候必须传入一个 MASViewAttribute
实例。从名字也可以看得出来,这个属性是作为约束中的 FirstView
的。
再来看看 equalTo
的实现,这也就是这个库的精髓所在:
1 | - (MASConstraint * (^)(id))equalTo { |
链式编程的核心在于每次调用的时候都返回自身。OC 的点语法相当于调用属性的 get 方法,因此是无法传参的。make.top.equalTo(superview.mas_top)
相当于通过 equalTo
的 get 方法,返回了一个 block 实例,然后再执行这个 block,返回自身的实例。
链式编程的一般使用场景在于设置一个对象的多个属性。
比如你要设置一个 button,你要设置它的多个属性就需要在多行里分别设置。但是如果 button 的每个属性的设置都返回 button 自身,那么就可以在一行中链式的调用。
同样的,offset
方法也是一样的道理。
设置约束
一条链式调用会创建一个 MASViewConstraint
对象。每一个约束都会保存到 MASConstraintMaker
的数组中。在设置完约束后,就来到了 [constraintMaker install]
添加约束:
1 | // MASConstraintMaker |
这里之前的两个标记就派上用场了,如果要重设就会先移除约束再重新添加,而如果是更新,就会把标记传入每一个约束中。MASViewConstraint
中添加约束:
1 | // MASViewConstraint |
源码比较长,但是思想还是很明确的,就是调用 iOS 提供的 NSLayoutConstraint
方法生成约束,然后找到两个视图的公共父节点,将约束添加到公共父节点上。
MASCompositeConstraint
最后,来瞧一下 MASViewConstraint
的子类 MASCompositeConstraint
。之前我们省略的代码都是针对它的。简单的说,MASCompositeConstraint
就是 MASViewConstraint
的集合。
我们看一下这样设置约束的场景:
1 | make.height.and.width.equaltTo(@20) |
我们在 make.height
的时候,已经创建了一个 MASViewConstraint
实例。所以执行到 .width
的时候,调用的是 MASViewConstraint
的 width
方法。我们来对比一下两者的不同:
1 | // MASConstraintMaker |
两者的不同在于,用 MASConstraintMaker
创建的约束传入的是 nil,而通过 MASViewConstraint
创建的约束传入的是自身。接着来看下面的处理方法:
1 | - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { |
当 constraint
不为 nil 的时候,就会把原来的约束和现在的约束作为一个数组,创建一个 MASCompositeConstraint
实例。随后替换 MASConstraintMaker
中相应的值。
相应的,install
的时候,需要对 MASCompositeConstraint
中的所有约束调用 install
方法:
1 | // MASCompositeConstraint |
总结
约束的创建本身涉及到很多的属性设置,Masonry 使用链式语法的方式精简了大量的代码。整体而言,Masonry 并不是一个太难理解的库。