该文章阅读的Masonry的版本为1.1.0。编程
原本不想贴直接使用原生API的实现方式,可是文章写到一半发现没有对原生API的解释,Masonry
的实现也不太好解释,因而就添加了这个第0节,对NSLayoutConstraint
这个类稍微介绍一下。数组
若是咱们直接用官方提供的NSLayoutConstraint
类进行布局,应该这样写:bash
UIView *redView = [UIView new];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100.0];
NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:200.0];
NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:200];
NSLayoutConstraint *constraint4 = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
NSArray<NSLayoutConstraint *> *constraints = @[constraint1, constraint2, constraint3, constraint4];
[self.view addConstraints:constraints];
复制代码
autolayout
的控件的translatesAutoresizingMaskIntoConstraints
属性设置为NO
,这个属性默认是YES
。NSLayoutConstraint
类提供的工厂方法constraintWithItem: attribute: relatedBy: toItem: attribute: multiplier: constant:
建立约束对象。addConstraints:
方法,将约束对象添加到控件上。其实直接使用NSLayoutConstraint
添加约束并不难也很好理解,就是太冗杂了。咱们重点来看NSLayoutConstraint
类实例化对象的工厂方法:布局
+(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(nullable id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c;
复制代码
看这个方法的目的是理解其各个参数的意义,这样在接下里看Masonry
时,就能好理解的多:ui
view1.attr1 <relation> multiplier × view2.attr2 + c
。view1
:是指要设置的约束的目标视图。attr1
:是指view1要设置约束的属性,是视图的顶部、宽度,仍是其余什么的。relation
:是指两个视图属性的关系,一共有三种,分别是不大于、等于和不小于。view2
:是指要设置约束的参考视图。attr2
:是指view2要设置约束的属性。multiplier
:是指约束要乘的倍率。c
:是指约束要加的大小使用Masonry
布局就简洁不少,一样的布局以下:spa
UIView *redView = [UIView new];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(200.0, 100.0));
make.top.equalTo(self.view).offset(50.0);
make.centerX.equalTo(self.view);
}];
复制代码
经过上一节能够看到全部的布局都是在mas_makeConstraints:
方法中进行的,点击方法进入查看实现:code
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
// 用autolayout布局要设置为NO
self.translatesAutoresizingMaskIntoConstraints = NO;
// 建立约束建立者对象
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
// 经过block回调约束建立者对象
block(constraintMaker);
// 返回全部添加的约束
return [constraintMaker install];
}
复制代码
在这个方法的实现中,就是建立一个管理约束的对象,而后经过block回调用以添加约束,添加完成后设置添加的约束。对象
这个方法更像是建立了一个用于设置约束的环境,用户只须要经过block设置约束便可,其余的都不须要操心。ip
咱们能够经过MASConstraintMaker
类对象提供的属性为控件添加各类各样的约束,咱们选取一个来查看其具体实现:rem
make.top.equalTo(self.view).offset(50.0);
复制代码
在上一节中,咱们已经知道了对象make
是MASConstraintMaker
类型,因此直接进入MASConstraintMaker
类中查看其top
属性的实现:
- (MASConstraint *)top {
// 调用了另外一个方法,并把要设置约束的位置传递过去
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
复制代码
继续点击查看:
- (MASConstraint *)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];
// 若是传入的约束对象是MASViewConstraint类及其子类
if ([constraint isKindOfClass:MASViewConstraint.class]) {
// 利用已经添加的约束对象和新添加的约束对象建立多视图约束封装对象
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;
}
复制代码
在这一步中,能够看到会返回一个MASViewConstraint
类或MASCompositeConstraint
类的对象,这两个类都是MASConstraint
的子类,能够说是“兄弟类”,它们保存了约束属性及其所在的视图,也就是 view1
和 attr1
。
- (MASConstraint * (^)(id))equalTo {
// 返回一个返回值类型为id,参数类型是id的block
return ^id(id attribute) {
// 调用下面的方法添加约束关系
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
复制代码
在上面的代码中咱们看到make.top
返回的是MASConstraint
的子类MASViewConstraint
或MASCompositeConstraint
类,因此在equalTo
这个方法中调用的实际上是MASViewConstraint
类或MASCompositeConstraint
类的对象方法equalToWithRelation
。
先看MASViewConstraint
类对这个方法的实现:
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
// 返回一个返回值为id类型,参数为id和NSLayoutRelation类型的block
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 {
// 若是传入的属性不是数组类型的
// 不能重复设置约束关系
// 若是已经设置了约束关系,必须和原约束关系相同,而且属性必须是NSValue类型的
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
// 保存约束关系和约束属性
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
// 返回当前类对象
return self;
}
};
}
复制代码
接着看一下MASViewConstraint
类中两个setter
的实现:
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
// 除了保存约束关系,还保存了是否设置了约束关系
_layoutRelation = layoutRelation;
self.hasLayoutRelation = YES;
}
复制代码
这个方法没啥好说的,就是保存了一下。
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
// 若是参数是NSValue类型的,根据值的类型设置不一样的属性
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
// 若是参数是UIView类型的,就生成 view2 的视图属性封装对象,其中 attr2 和 view1 的 view1 相同,并保存
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
// 若是参数是MASViewAttribute类型的,就直接保存
_secondViewAttribute = secondViewAttribute;
} else {
// 只容许输入 NSValue 、 UIView 和 MASViewAttribute 这三种类型的数据
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
}
复制代码
这个方法中的三个条件其实分别对应下面的三种输入状况:
make.width.equalTo(@200);
make.centerX.equalTo(self.view);
make.left.equalTo(self.view.mas_left);
在这一步中,实际作的工做就是保存布局关系和约束的参考视图,也就是 relation
、 view2
以及 attr2
。但有一点须要注意的是Masonry
经过将方法的返回值设置成一个返回值是当前类类型的block,来实现链式编程的效果。
MASConstraint
的实现:- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}
复制代码
是否是熟悉的配方?是否是熟悉的味道?和equalTo
同样,都是经过返回一个返回值是id
类型的block来实现链式编程的效果。其中的实现也很简单,就是保存了一下传入的参数。
MASViewConstraint
中的实现:- (void)setOffset:(CGFloat)offset {
self.layoutConstant = offset;
}
复制代码
- (void)setLayoutConstant:(CGFloat)layoutConstant {
_layoutConstant = layoutConstant;
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
if (self.useAnimator) {
[self.layoutConstraint.animator setConstant:layoutConstant];
} else {
self.layoutConstraint.constant = layoutConstant;
}
#else
self.layoutConstraint.constant = layoutConstant;
#endif
}
复制代码
在这个子类中就是保存了一下传入的常数。
MASCompositeConstraint
中的实现:- (void)setOffset:(CGFloat)offset {
// 遍历全部视图属性封装对象,设置参数
for (MASConstraint *constraint in self.childConstraints) {
constraint.offset = offset;
}
}
复制代码
这一步就是设置常数,也就是 c
。
到此为止,约束所需的参数都设置完成,下面就是设置约束了。
在第 2 节中 mas_makeConstraints:
方法的最后一句就是设置约束:
return [constraintMaker install];
复制代码
咱们点进 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;
}
复制代码
逻辑很明白,就是先移除以前已经添加的约束,在设置要设置的约束。
而后看一下解除约束的方法实现:
- (void)uninstall {
// 这个为了兼容 iOS8 以前的版本,由于属性 active 是iOS8 才开始生效的
if ([self supportsActiveProperty]) {
// 将约束的活动状态设置为NO
self.layoutConstraint.active = NO;
// 从保存集合中移除
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
// 返回
return;
}
// 若是是 iOS8 以前的版本
// 移除掉视图的约束
[self.installedView removeConstraint:self.layoutConstraint];
// 属性置空
self.layoutConstraint = nil;
self.installedView = nil;
// 从保存集合中移除
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}
复制代码
接着看设置约束的方法实现:
- (void)install {
// 先判断当前约束是否已被设置,若是设置了就不须要继续向下执行了
if (self.hasBeenInstalled) {
return;
}
// 一样是为了兼容 iOS8
if ([self supportsActiveProperty] && self.layoutConstraint) {
// 将约束的活动状态设置为YES
self.layoutConstraint.active = YES;
// 将约束添加到集合中保存
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
// iOS7 以前版本的设置方式
// 获取设置的各个参数
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
// 若是设置了像 make.left.equalTo(@10) 这样的约束
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
// view2 就是 view1 的父视图
secondLayoutItem = self.firstViewAttribute.view.superview;
// attr2 就是 attr1
secondLayoutAttribute = firstLayoutAttribute;
}
// 建立约束对象
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
// 设置优先级和key
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
if (self.secondViewAttribute.view) {
// 若是设置了 view2
// 获取 view1 和 view2 的公共父视图
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) {
// 若是设置的属性为 size 类型的, 要设置约束的视图就是 view1
self.installedView = self.firstViewAttribute.view;
} else {
// 不然,要设置约束的视图就是 view1 的父视图
self.installedView = self.firstViewAttribute.view.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];
}
}
复制代码
这个方法里面还有个比较两个约束是否类似的方法:
- (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint {
// 遍历要设置约束视图的全部已设置的约束
for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) {
// 若是已设置的约束不是 MASLayoutConstraint 类型的,跳过
if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue;
// 若是已设置的约束的 view1 和要设置约束的 view1 不相同,跳过
if (existingConstraint.firstItem != layoutConstraint.firstItem) continue;
// 若是已设置的约束的 view2 和要设置约束的 view2 不相同,跳过
if (existingConstraint.secondItem != layoutConstraint.secondItem) continue;
// 若是已设置的约束的 attr1 和要设置约束的 attr1 不相同,跳过
if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue;
// 若是已设置的约束的 attr2 和要设置约束的 attr2 不相同,跳过
if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue;
// 若是已设置的约束的 relation 和要设置约束的 relation 不相同,跳过
if (existingConstraint.relation != layoutConstraint.relation) continue;
// 若是已设置的约束的 multiplier 和要设置约束的 multiplier 不相同,跳过
if (existingConstraint.multiplier != layoutConstraint.multiplier) continue;
// 若是已设置的约束的优先级和要设置约束的优先级不相同,跳过
if (existingConstraint.priority != layoutConstraint.priority) continue;
// 返回幸存者,也就是除了常数 c 其余参数都要相同
return (id)existingConstraint;
}
// 若是没有符合条件的就返回空对象
return nil;
}
复制代码
View+MASAdditions.h
分类中的 mas_makeConstraints:
方法用来建立设置约束的环境,以及获取约束工厂类 MASConstraintMaker
的对象 make
。make
的属性设置约束的属性 attr1
。例如 make.top
。这个时候在 make
对象内部:
MASViewAttribute
对象,里面封装着 view1
和 attr1
。MASViewConstraint
对象并返回,这个对象就负责管理着 NSLayoutConstraint
类对象。view2
和 attr2
。例如 make.top.equalTo(redView.mas_bottom)
。这时在MASViewConstraint
对象内部:
MASViewAttribute
对象,里面封装着 view1
和 attr1
。make.top.equalTo(redView.mas_bottom).offset(30.0);
。在这一步中,MASViewConstraint
对象只是保存了一下传入的参数。mas_makeConstraints:
方法中,就会调用 make
对象的 install
方法。make
对象会调用刚才建立的 MASViewConstraint
对象的install
方法。MASViewConstraint
对象的install
方法中,经过刚才设置的约束的参数建立 NSLayoutConstraint
对象,并添加到视图上。