Masonry简介
Masonry是用于自动布局的第三方框架,对苹果的自动布局框架进行了一层封装,其接口比起官方的接口来,显得非常简洁。
常用接口
常用接口主要有以下三个:
@interface MAS_VIEW(MASAddtions)
// make constraint
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make)block;
// update constraint
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make)block;
// remake constraint
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make)block;
@end
mas_makeConstraints接口
mas_makeConstraints
接口是用来创建约束的,该方法实现如下:
- (NSArray *)mas_makeConstraints:(void(^)(MSAConstraintMaker *make)block{
self.translatesAutoresizingMaskInfoConstraints = NO;
MSAConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
该方法主要做了以下几件事:
- 设置属性
translatesAutoresizingMaskInfoConstraints
为NO
,该属性代表用来开启自动布局; - 创建
MASConstraintMaker
的实例constraintMaker
,并传给block
; - 调用
[constraintMaker install]
。
MSAConstraintMaker
MSAConstraintMaker
封装了创建约束的工厂方法,并最终调用install
将约束‘安装’起来。block(constraintMaker)
是用于配置constraintMaker
的相关属性,假设有以下代码make.left.mas_equal(0);
,那是对constraintMaker.left
属性进行配置,我们来看下该代码在MSAConstraintMaker.m
中的实现。
@property(nonatomic, strong, readonly) MASConstraint *left;
- (MASConstraint *)left{
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MSAConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute{
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute{
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
newConstraint.delegate = self;
[self.constraint addObject:newConstraint];
return newConstraint;
}
从上面可以看出,通过make.left
最终调用了constraint:addConstraintWithLayoutAttribute
函数,生成了一个MASViewConstraint
的实例,之后left.mas_equal(0)
则是对MASViewConstraint
的操作。
在介绍MASViewConstraint
之前,我们先看下[constraintMaker install]
是怎样实现的:
- (NSArray *)install{
if(self.removeExisting){
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for(MASConstraint *constraint in installedConstraints){
[constraint uninstall];
}
}
NSArray *constraints = self.constraints.copy;
for(MASConstraint *constraint in constraints ){
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[self.constraints removeAllObjects];
return constraints;
}
install
其实是遍历self.constraints
,对所有MASConstraint
进行install
。
另外两个常用的更新和重新设置约束方法:
/// 更新约束
- (NSArray *)mas_updateConstraints:(void(^)(MSAConstraintMaker *make)block{
self.translatesAutoresizingMaskInfoConstraints = NO;
MSAConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.updateExisting = YES; // 标记位更新已存在的
block(constraintMaker);
return [constraintMaker install];
}
/// 重新设置约束
- (NSArray *)mas_remakeConstraints:(void(^)(MSAConstraintMaker *make)block{
self.translatesAutoresizingMaskInfoConstraints = NO;
MSAConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.removeExisting = YES; // 标记为需要移除之前的
block(constraintMaker);
return [constraintMaker install];
}
MASConstraint
MASConstraint
提供链式语法来创建约束,比如.mas_equal(view).mas_equal(0);
这种形式,用来设置一些约束的属性,其实现如下:
- (MASConstraint *(^)(CGFloat)offset{
return ^id(CGFloat offset){
self.offset = offset;
return self;
}
}
- (MASConstraint *)width{
return self;
}
MASViewAttribute
MASViewAttribute
用于存储view
和对应的NSLayoutAttribute
。
MASViewConstraint
之前提到MSAConstraintMaker
会创建MASViewConstraint
,这个类继承自MASConstraint
,代表单个constraint
。在mas_makeConstraint
中最后一个步骤是调用[constraintMaker install]
,而这个方法正是对MSAConstraintMaker
中的每个MASViewConstraint
依次调用install
,其实现如下:
- (void)install{
if(self.hasBeenInstalled){
return;
}
if([self supportsActiveProperty] && self.layoutConstraint){
self.layoutConstraint.active = YES;
[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 = firstLayoutItem.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
MALayoutConstraint *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;
if(self.secondViewAttribute.view)
self.installedView = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
else if(self.firstViewAttribute.isSizeAttribute)
self.installedView = self.firstViewAttribute.view;
else
self.installedView = self.firstViewAttribute.superview;
MASLayoutConstraint *existingConstraint = nil;
if(self.updateExisting){
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if(existingConstraint){
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
}else{
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
该方法主要流程如下:
- 如果
hasBeenInstalled
已经标记过了,则代表该约束已经添加过了,直接return
; - 如果支持
NSLayoutConstraint.active
(iOS8.0+)并且self.layoutConstraint
已存在,则直接设置为active
即可。 - 获取约束的
firstItem
和secondItem
,如果secondItem
不存在,将seconItem=firstItem.superview
,默认跟父view
创建约束关系; - 生成约束,
[MASLayoutConstraint constraintWithItem:firstLayoutItem attribute:firstLayoutAttribute relatedBy:self.layoutRelation toItem:secondLayoutItem attribute:secondLayoutAttribute multiplier:self.layoutMultiplier constant:self.layoutConstant]
; - 让约束生效,将生成的约束加到对应的
view
上。
对应的方法是uninstall
,将约束移除,其实现如下:
- (void)uninstall{
if([self supportActiveProperty]){
self.layoutConstraint.active = NO;
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
return;
}
[self.installedView removeConstraint:self.layoutConstraint];
self.layoutConstraint = nil;
self.installedView = nil;
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}
MASCompositeConstraint
MASCompositeConstraint
是MASViewConstraint
的集合。
实例
接下来分析一个例子,看看是如何创建约束的:
[self mas_makeConstraints:^(MASConstraintMaker *maker){
make.left.mas_equal(10).priorityLow();
}];
通过之前的分析,我们将上面代码展开,得到以下代码:
self.translatesAutoresizingMaskInfoConstraints = NO;
MSAConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.left.mas_equal(10).priorityLow();
[constraintMaker install];
constraintMaker.left.mas_equal(10).priorityLow()
- 首先是
constraintMaker.left
,返回了一个MASConstraint
对象,展开是这样的:
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeft];
MASViewConstraint *masConstraint= [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
masConstraint.mas_equal(10).priorityLow();
-
mas_equal
其实是一个宏,定义如下:
#define mas_equalTo(...) equal(MASBoxValue((__VA_ARGS)))
#define MASBoxValue(value) _MASBoxValue(@encode(_typeof__((value))), (value))
而_MASBoxValue
是通过对象的encode
类型转换成对应的NSNumber
和NSValue
。
这样的话代码就变成这样了:
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeft];
MASViewConstraint *masConstraint= [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
id value = _MASBoxValue(@encode(__typeof__(10), 10);
masConstraint.equal(value).priorityLow();
-
equal
和priorityLow
其实是设置属性的,展开如下:
masConstraint.equal(@10);
-> masConstraint.equalWithRelation(@0, NSLayoutRelationEqual);
-> masConstraint.layoutRelation = NSLayoutRelationEqual;
[masConstraint setSecondViewAttribute:@10]
-> masConstraint.offset = @10; // 由于这里是NSNumber,就设置为offset了,如果是UIView,则初始化为secondViewAttribute
masConstraint.priorityLow();
-> masConstraint.layoutPriority = MASLayoutPriorityDefalutLow;
- 此时
masConstraint
的属性是这样的:
firstViewAttribute.item -> self
firstViewAttribute.layoutAttribute -> NSLayoutAttributeLeft
secondViewAttribute.item -> self.superview
secondViewAttribute.layoutAttribute -> NSLayoutAttributeLeft
layoutRelation -> NSLayoutRelationEqual
layoutMultiplier -> 1.0
layoutConstant -> 10
layoutPriority -> MASLayoutPriorityDefalutLow
- 调用
install
方法创建约束:
MALayoutConstraint *layoutConstraint = [MASLayoutConstraint constraintWithItem:self attribute:firstLayoutAttribute relatedBy:NSLayoutAttributeLeft toItem:self.superview attribute:NSLayoutAttributeLeft multiplier:1 constant:10];
layoutConstraint.priority = MASLayoutPriorityDefalutLow;
- 这样约束就成功创建完成了。
小结
Masonry
对外提供的接口非常易用,但其实现因为大量用了block回调的原因,还是要花点时间才能读懂。