想必在AFNetworking之后,Masonry成了广大iOS开发者日常开发不可或缺的三方库之一。它的使用真的非常简单,例如:
[greenView makeConstraints:^(MASConstraintMaker *make) {
make.top.greaterThanOrEqualTo(superview.top).offset(padding);
make.left.equalTo(superview.left).offset(padding);
make.bottom.equalTo(blueView.top).offset(-padding);
make.right.equalTo(redView.left).offset(-padding);
make.width.equalTo(redView.width);
make.height.equalTo(redView.height);
make.height.equalTo(blueView.height);
}];
复制代码
从这段短小精悍的代码中,我们已经能够挖掘出它背后的原理。本文假设你对Masonry已经有基本的了解和使用。
首先我们提取一些“关键词”,他们究竟是啥意思?
1.makeConstraints:
2.make
3.left、right、height...
4.equalTo()
1.makeConstraints:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
复制代码
-
MASConstraintMaker
被传入了block()中,即MASConstraintMaker
实例就是我们的第二个“关键词”---make
。从[constraintMaker install]
还可看出,make负责了约束的添加。后文详细介绍make
。 -
translatesAutoresizingMaskIntoConstraints
,官方解释大致为:默认情况下它是YES
,即view
的autoresizing mask
会自动成为它的布局。如果我们希望手动布局,需要将它设为NO
2.make(MASConstraintMaker)
从链式调用make.top.equalTo...
,我们可以看出,make
中定义了这些约束属性,它们是这样实现的:
// step 1
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
// step 2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
// step 3
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
复制代码
-
MASViewAttribute
从实现文件中得知,MASViewAttribute = View + NSLayoutAttribute + item
,这是一个可变元组,存储了View
和与它相关的约束信息。 -
MASViewConstraint
这就是一个约束,它包含firstViewAttribute
和secondViewAttribute
。 -
由step2可知,单纯的约束属性在该方法下的第一个参数都是
nil
,所以我们先直接看这种情况下的step3的执行情况。它被加入了一个约束数组中。
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
复制代码
4)链式调用的实现
我们知道Masonry
链式调用是它广受好评的优点之一。那么,是如何做到make.top.left
这样的操作呢?
由step3得知,make.top
的返回类型是MASViewConstraint
,那MASViewConstraint
中是如何调用到left的呢?
从MASViewConstraint
的父类MASConstraint
可以看到,这里也定义了所有的布局属性,而这些布局属性的实现方式,如下,例:
// MASConstraint.m
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
// MASViewConstraint.m
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
复制代码
MASViewConstraint
把这个方法的具体实现委托给了代理方法。而在step3中,这个代理正是MASConstraintMaker
。不同的是,此时,step2方法的参数不在是nil
了,而是当前约束属性。这也是的step3的处理逻辑不一样了。(回到step3中再看看!)此时,MASCompositeConstraint
登场了,它是一个约束组合。以make.top.left...
为例,这个约束组合包含了top
和left
,并且调用了shouldBeReplacedWithConstraint:
方法,如下:
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.constraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
复制代码
以make.top.left...
为例,这个方法找到了之前存储top
约束的位置,并替换成了约束组合。最终,top
和left
就一起被加入和make
的约束数组中。
3.install
从上文看,我们已经拿到了所有的约束。这些约束是如何加入到视图上的?来看看install
方法。
摘录一下MASConstraintMaker
中的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;
}
复制代码
make
的处理相对简单,再看看MASViewConstraint
本身是如何install
的。由于方法太长,我不粘贴完整的代码了(分开来会比较容易看懂)。大致的流程如下:
1.如何约束是设置width
和height
,这是视图自身的属性,则把当前视图的父视图作为关联视图(代码中为secondLayoutItem
)。即这两个约束是相对于父视图设置的。
2.如果布局上存在相对视图,即通常写法中的equalTo(someView.mas_top)
这样,则找到这2个视图最近的公共父视图,并把约束添加在这个父视图上。代码如下:
if (self.secondViewAttribute.view) {
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
NSAssert(closestCommonSuperview,
@"couldn't find a common superview for %@ and %@",
self.firstViewAttribute.view, self.secondViewAttribute.view);
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
self.installedView = self.firstViewAttribute.view;
} else {
self.installedView = self.firstViewAttribute.view.superview;
}
复制代码
3.最后,如果是更新约束操作,则找出需要更新的约束单独修改;否则,为视图添加约束,并记录在mas_installedConstraints
中。
到这里,约束的添加已经完全梳理明白了。
4.equalTo()
还是得看具体实现方法:
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
NSMutableArray *children = NSMutableArray.new;
for (id attr in attribute) {
MASViewConstraint *viewConstraint = [self copy];
viewConstraint.layoutRelation = relation;
viewConstraint.secondViewAttribute = attr;
[children addObject:viewConstraint];
}
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self.delegate;
[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
} else {
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}
复制代码
这里就比较好理解了,如果传入的是约束数组,则把他包装成MASCompositeConstraint
,以同样的方式加入到make
的constraints
中。如果是单个约束,则将其设为secondViewAttribute
,用于install
的时候使用。
总结
1.MASConstraintMaker
作为工厂,生产一个个MASViewConstraint
约束对象。
2.MASViewConstraint
和MASCompositeConstraint
继承于抽象类MASConstraint
,为我们提供了高度封装的约束对象
3.View+MASAdditions
这个UIView
的扩展是Masonry
与外界交互的接口类,这样很好的把复杂的约束逻辑封装在内部管理,又提供了简单的API供用户使用。
你看懂了嘛~