1. Masonry调用方式入门
#import "Masonry.h"
[self.containerView addSubview:self.newsView];
[self.newsView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.containerView);
make.height.equalTo(@(100));
}];
[self.containerView addSubview:self.bannerView];
[self.bannerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.height.equalTo(self.newsView);
make.top.equalTo(self.newsView.mas_bottom);
}];
2. Masonry调用解析
2.1 查看 mas_makeConstraints:
方法的实现
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
//关闭 Autoresize 布局
self.translatesAutoresizingMaskIntoConstraints = NO;
//创建 MASConstraintMaker 管理类
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
//保存外部传入的约束集合
block(constraintMaker);
//安装约束
return [constraintMaker install];
}
- 首先帮助我们关闭
Autoresize
布局(只能相对于父视图改变上左下右宽高的大小)- 然后创造一个管理器 MASConstraintMaker,和当前视图 UIView 建立 weak 链接
- 执行传给用户的block,同时将管理器传给用户
- 安装用户调用管理器创建的对象
- 可见,此处运用了外观模式,开发者只需要调用管理器的方法,而不需要了解内部情况。
2.2 通过 make.left
方法分析 MASConstraintMaker 类
make.left.right.height.equalTo(self.newsView);
//step1
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
//step2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
//step3
- (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]) {
//step4
//replace with composite constraint instead of MASViewConstraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
if (!constraint) {
//step5
// MASCompositeConstraint 类调用
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
//step6
// 创建对象
return newConstraint;
}
- 由于
constraint:addConstraintWithLayoutAttribute
方法传进来的参数是nil
,因此只能执行代码中的 step6return newConstraint;
,将当前←的属性保存到当前MASConstraint
对象中。
2.3 通过 make.left.right
方法分析 MASViewConstraint 类
make.left.right.height.equalTo(self.newsView);
- 在 MASViewConstraint 类种未找到 right 方法,发现它继承于父类 MASConstraint
@interface MASViewConstraint : MASConstraint
- MASConstraint类
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
//宏定义,如果子类未实现当前方法,就抛出异常
MASMethodNotImplemented();
}
父类 MASConstraint 中未实现 right 方法的底层,需要返回 MASViewConstraint 中查找 addConstraintWithLayoutAttribute: 方法
- MASViewConstraint类如下:
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
//调用代理 MASConstraintMaker 管理类中的方法
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
此处为了将当前链式结构的 left
、right
等方法都保存到一个对象中,保证 left
、right
等方法返回的对象是 MASConstraint,同时由同一个管理类来处理,是一个很好的架构设计。
不仅
MASConstraintMaker
对象中含有left
、right
等方法,MASConstraint
对象中也含有left
、right
等方法。
再回到 MASConstraintMaker
对象中,看以下语句的执行过程
- MASViewConstraint
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
- MASConstraintMaker
//step3
- (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]) {
//step4
//replace with composite constraint instead of MASViewConstraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
if (!constraint) {
//step5
// MASCompositeConstraint 类调用
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
//step6
// 创建对象
return newConstraint;
}
由于传进去了 MASViewConstraint 对象,会执行 step4,执行了以下操作:
- 将刚刚传入的
left
、right
装进数组- 创建
MASCompositeConstraint
对象- 将
MASCompositeConstraint
对象的代理设置给MASConstraintMaker
对象,便于继续执行暴露给开发者 的width
等方法- 用新创建的 MASCompositeConstraint 代替数组中的 MASViewConstraint 对象
- 返回
MASCompositeConstraint
对象给right
方法
- MASConstraintMaker
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
//获取当前 MASViewConstraint 所在的 index
NSUInteger index = [self.constraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
//用 MASCompositeConstraint 代替同一链条上的的 MASViewConstraint
[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
由于此处通过调用 MASViewConstraint 的 right 方法,来创建 MASCompositeConstraint 对象,因此需要保留最新的属性数组,方便给每条链条设置数值并安装约束。
2.4 通过 make.left.right.height
方法分析 MASCompositeConstraint 类
make.left.right.height.equalTo(self.newsView);
在 MASViewConstraint 类中也未找到 height 方法,发现它继承于父类 MASConstraint
@interface MASCompositeConstraint : MASConstraint
- MASConstraint类
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
MASMethodNotImplemented();
}
父类 MASConstraint 中也未实现 height 方法的底层,需要返回 MASCompositeConstraint 中查找 addConstraintWithLayoutAttribute: 方法
- MASCompositeConstraint类如下:
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
id strongDelegate = self.delegate;
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
newConstraint.delegate = self;
[self.childConstraints addObject:newConstraint];
return newConstraint;
}
操作如下:
- 获取约束对象
- 并将最新的约束对象保存到当前链条的约束数组中(已经有3条)
- 返回普通的
MASViewConstraint
对象
再回到 MASConstraintMaker 对象中,查看获取 MASConstraint 约束的过程
- MASCompositeConstraint
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
- MASConstraintMaker
//step3
- (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]) {
//step4
//replace with composite constraint instead of MASViewConstraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
if (!constraint) {
//step5
// MASCompositeConstraint 类调用
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
//step6
// 创建对象
return newConstraint;
}
直接执行了 step6 ,创建了约束对象,就立即返回给了 MASCompositeConstraint 对象,保存在 MASCompositeConstraint 对象内部的约束数组中。
2.5 通过 make.left.right.height.equalTo(self.newsView)
方法来分析 MASCompositeConstraint 类
在 MASViewConstraint 类中也未找到 equalTo 方法,发现它继承于父类 MASConstraint
@interface MASCompositeConstraint : MASConstraint
- MASConstraint类
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
父类 MASConstraint 中也未实现 equalToWithRelation 方法的底层,需要返回 MASCompositeConstraint 中查找该方法
- MASCompositeConstraint类如下:
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
在调用equal方法后,会遍历约束数组中的所有子约束对象,调用其equalToWithRelation方法,接下来,我们进入 MASViewConstraint 查看此方法
* MASViewConstraint
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
//step7
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 {
//step8
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}
由于传入的不是数组,只会走 step8,对约束设置和第二个约束对象的关系,如视图 A 的高度相对于它的父视图的高度之间的关系。
更新第二个约束对象信息的代码如下:
- MASViewConstraint
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
//更新约束对象,同时设置相对值
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
//使用原对象的约束属性,如make.left.mas_equalTo(self)
//参考的是第二个对象的left
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
//直接更新约束对象
_secondViewAttribute = secondViewAttribute;
} else {
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
}
- MASConstraint
- (void)setLayoutConstantWithValue:(NSValue *)value {
if ([value isKindOfClass:NSNumber.class]) {
self.offset = [(NSNumber *)value doubleValue];
} else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
CGPoint point;
[value getValue:&point];
self.centerOffset = point;
} else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
CGSize size;
[value getValue:&size];
self.sizeOffset = size;
} else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets insets;
[value getValue:&insets];
self.insets = insets;
} else {
NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
}
}
2.6 查看 install
方法的实现
- MASConstraintMaker
- (NSArray *)install {
if (self.removeExisting) {
//remake移除重复约束
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;
}
- MASCompositeConstraint
- (void)install {
for (MASConstraint *constraint in self.childConstraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
}
- MASViewConstraint
- (void)install {
if (self.hasBeenInstalled) {
return;
}
if ([self supportsActiveProperty] && self.layoutConstraint) {
self.layoutConstraint.active = YES;
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
//step11
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
// alignment attributes must have a secondViewAttribute
// therefore we assume that is refering to superview
// eg make.left.equalTo(@10)
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
// 默认为原约束对象的父视图superview及其约束属性如left
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
//step12
MASLayoutConstraint *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) {
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) {
//update installedView for width or height
self.installedView = self.firstViewAttribute.view;
} else {
//update installedView as superview
self.installedView = self.firstViewAttribute.view.superview;
}
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
//update layout constraint
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
使用面向对象的方式代替了系统添加约束的方式
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];