带着问题去思考,更加深入的理解Masonry。我们来看看Masonry常见的问题。
问题目录:
1.没有添加view,就使用了masonry布局。为什么会崩溃?
2.调用left.equalTo方法后不能继续设置width?
3.为什么mas_updateConstraints只能对已存在的约束更新?如果是不存在的约束控制台会为什么会报错?
4.1同一个view使用多个mas_makeConstraints会不会有问题?为什么?
4.2 接着问题4.1,什么时候需要分开写呢?
5.make约束完后什么时候能取到正确的frame?
6.怎么获取已经使用约束的值?
7.使用mas_makeConstraints为什么不需要__weak引用?
问题1
1.1没有添加view,就使用了masonry布局。
UIView * view = [[UIView alloc] init];
view.backgroundColor = [UIColor redColor];
// [self.view addSubview:view];
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(10);
make.right.equalTo(self.view).offset(-10);
make.top.equalTo(self.view).offset(10);
make.bottom.equalTo(self.view).offset(-10);
}];
1.2原因
closestCommonSuperview不能为空,作者用NSAssert方式让使用者更好调试。
1.3 mas_closestCommonSuperview
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
MAS_VIEW *closestCommonSuperview = nil;
MAS_VIEW *secondViewSuperview = view;
while (!closestCommonSuperview && secondViewSuperview) {
MAS_VIEW *firstViewSuperview = self;
while (!closestCommonSuperview && firstViewSuperview) {
if (secondViewSuperview == firstViewSuperview) {
closestCommonSuperview = secondViewSuperview;
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
return closestCommonSuperview;
}
这个方法是查找约束view与添加的view父视图。
如[self.view addSubview:view];则closestCommonSuperview返回的是self.view;而不添加,则是查找view与nil的父视图,直接返回为空。
问题2
2.调用left.equalTo方法后不能继续设置width
//错误写法
make.left.equalTo(self.view).offset(10).width.mas_equalTo(ScreenWidth-20);
//正确写法
make.left.equalTo(self.view).offset(10);
make.width.mas_equalTo(ScreenWidth-20);
需要了解hasLayoutRelation为什么是NO,正常添加width布局时为什么是YES?
从逻辑上看Masonry支持链式, 错误写法在语法上没有错误。
看下面这段源码:
- (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;
}
这段代码在#pragma mark - standard Attributes的方法都会调用,如make.left、make.center。
每一条make.语法都会产生一个新的MASViewConstraint对象。
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
....
} else {
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
_layoutRelation = layoutRelation;
self.hasLayoutRelation = YES;
}
- equalToWithRelation返回的是MASViewConstraint对象
- 而每一条MASViewConstraint只要设置了equalTo(包括mas_equalTo等,只要调用了self.equalToWithRelation的block)方法就会把self.hasLayoutRelation置为YES.
- 而接着调用width不是MASConstraintMaker中的width方法,而是MASViewConstraint的width方法。
所以在调用equalToWithRelation方法后,要设置left、width等属性,需要重新make.语法。
问题3
3.为什么mas_updateConstraints只能对已存在的约束更新?如果是不存在的约束控制台会为什么会报错?
- (void)install {
...
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
//判断MASLayoutConstraint属性是否相等
- (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint {
// check if any constraints are the same apart from the only mutable property constant
// go through constraints in reverse as we do not want to match auto-resizing or interface builder constraints
// and they are likely to be added first.
for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) {
if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue;
if (existingConstraint.firstItem != layoutConstraint.firstItem) continue;
if (existingConstraint.secondItem != layoutConstraint.secondItem) continue;
if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue;
if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue;
if (existingConstraint.relation != layoutConstraint.relation) continue;
if (existingConstraint.multiplier != layoutConstraint.multiplier) continue;
if (existingConstraint.priority != layoutConstraint.priority) continue;
return (id)existingConstraint;
}
return nil;
}
- mas_updateConstraints中有个变量updateExisting标识是否更新,逆序查找self.installedView.constraints,所有属性都相等时返回existingConstraint.
- existingConstraint.constant = layoutConstraint.constant;这段就是更新已存在约束
- 假如updateExisting为YES,而existingConstraint不存在,则走了[self.installedView addConstraint:layoutConstraint]。导致view的约束会报错。这段应该是个bug,可以用下面代码修复:
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
}
NSAssert(existingConstraint != nil, @"existingConstraint 不存在,请检查约束");
}else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
错误的约束应该让开发者去处理,在项目复杂的时候,看到控制台大量约束错误,却不知道是那个view约束报错,这是令人头疼的事情。
问题4
4.1同一个view使用多个mas_makeConstraints会不会有问题?为什么?
UIView * view = [[UIView alloc] init];
view.backgroundColor = [UIColor redColor];
[self.view addSubview:view];
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(10);
make.width.mas_equalTo(ScreenWidth-20);
}];
[view mas_makeConstraints:^(MASConstraintMaker *make){
make.top.equalTo(self.view).offset(10);
make.bottom.equalTo(self.view).offset(-10);
}];
答案是不会有问题。
- (void)install {
...
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;
}
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
}
NSAssert(existingConstraint != nil, @"existingConstraint 不存在,请检查约束");
}else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
- self.installedView是view与self.view的第一个共同的view。
- 两次mas_makeConstraints产生了两个MASConstraintMaker对象,但view与self.view没有变,所以两次查找的self.installedView是相同的。
- make.left、make.width、make.top、make.bottom都是添加到self.installedView上。
综上:两个mas_makeConstraints的与一个mas_makeConstraints效果上是一样的,而有时候就需要分开写。
4.2 接着问题4.1,什么时候需要分开写呢?
UIView * view = [[UIView alloc] init];
view.backgroundColor = [UIColor redColor];
[self.view addSubview:view];
UIView * view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor greenColor];
[self.view addSubview:view2];
[@[view,view2] mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(10);
make.width.mas_equalTo(ScreenWidth-20);
make.height.mas_equalTo(200);
}];
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).offset(10);
}];
[view2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(view.mas_bottom).offset(10);
}];
假如有多个view其中大部分属性是相同的,但又有细微差异。我们可以统一约束相同部分,再针对不同部分单独约束。
问题5
5.make约束完后什么时候能取到正确的frame?
需要理解的:Masonry实际是调用了系统的constraintWithItem,而系统的方法最终都会转成frame.约束完后不会立即刷新界面,需要等待下一个runloop才能刷新。如果在make后直接获取frame,frame是不正确的。
- 在viewController中viewDidLayoutSubviews中可正确获取frame
- 调用layoutIfNeeded方法后可正确获取frame
问题6
6.怎么获取已经使用约束的值
每一条make语句都会生成MASViewConstraint,layoutConstant就是每条约束的值。下面是获取约束的值:
#import "UIView+Masonry.h"
#import "Masonry.h"
@implementation UIView (Masonry)
//获取约束的值
- (CGFloat)getOffsetWithAttribute:(NSLayoutAttribute)attribute{
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self];
for (MASViewConstraint * constraint in installedConstraints) {
MASViewAttribute * firstViewAttribute = constraint.firstViewAttribute;
if (firstViewAttribute.layoutAttribute == attribute) {
CGFloat offset = [[constraint valueForKey:@"layoutConstant"] floatValue];
return offset;
}
}
return 0;
}
@end
问题7
7.使用mas_makeConstraints为什么不需要__weak引用?
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
这个问题很常见,但见到很多人都不理解。block是个局部变量,不会产生循环引用。
总结:
- 带着问题去查看源码,比直接阅读第三方源码理解的更加透彻。
- 如果想更透彻的理解Masonry,建议先把链式理解。