一个有趣的实验
首先做个简单的小实验,在storyboard拖放一个view,添加好约束。之后利用masonry分别去更新这个视图的位置,尺寸。会发现不一样的结果。
//更新尺寸- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event{ [super touchesBegan:touches withEvent:event]; [self.viewTest mas_updateConstraints:^(MASConstraintMaker *make) { //和storyboard中的约束相同 make.width.mas_equalTo(375.0); }];}//更新位置- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ [super touchesBegan:touches withEvent:event]; [self.viewTest mas_updateConstraints:^(MASConstraintMaker *make) { //和storyboard中的约束相同 make.left.equalTo(self.viewTest.superview); }];}复制代码
如果你这么做会惊奇的发现更新位置有效,更新尺寸无效。why?到底用masonry能不能更新xib上的约束?带着这个问题我们去深入探索。
探索
第一步:查看源代码
其实上面两个小实验控制台都有打印警告
大致意思是说约束冲突。分析控制台中的两个约束,第二个应该是xib上的约束(有个55常量),第一个是我们更新后多出来的(MASLayout)。那说明mas_updateConstraints导致多出来一个约束导致了约束冲突。让我们看看源代码。
//调用代码- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; //更新存在的约束 constraintMaker.updateExisting = YES; block(constraintMaker); return [constraintMaker install];}//最终要执行的代码- (void)install { if (self.hasBeenInstalled) { return; } ....... MASLayoutConstraint *existingConstraint = nil; //判断是否更新存在约束(刚刚这里赋值了YES) if (self.updateExisting) { //下边去当前view上的约束里去匹配传过来的约束,如果相同返回这个约束,否则nil existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint]; } //找到了匹配的约束 if (existingConstraint) { // just update the constant //更新约束的值 existingConstraint.constant = layoutConstraint.constant; self.layoutConstraint = existingConstraint; } else { //没找到匹配约束就在当前view上添加外边传过来的约束 [self.installedView addConstraint:layoutConstraint]; self.layoutConstraint = layoutConstraint; [firstLayoutItem.mas_installedConstraints addObject:self]; }}//遍历匹配当前view上的约束- (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. /** 大致意思是倒序遍历查找约束,过滤掉了auto-resizing,interface builder 上的约束,也就是说遍历时候不考虑xib上的约束这样碰到xib上的约束后就直接continue了,最终会找不到匹配的约束返回nil */ 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;}复制代码
通过上面分析我们知道了,xib上的约束是不去匹配的最终existingConstraint被赋值了nil。导致最终执行了[self.installedView addConstraint:layoutConstraint];新加了一个约束。而这个新的约束往往是最高优先级的,并且xib上的约束发现默认也是最高优先级所以导致了约束冲突。控制台打印了警报。
第二步:验证
步骤一分析得出的结论是mas_updateConstraints方法导致新增了一个同优先级的约束导致了约束冲突。那我们尝试去改一下xib上的相应约束的优先级,调低些。在运行代码发现控制台并没有报错输出,同时无论更新尺寸还是位置都有效。这也进一步验证了步骤一的结论。
总结
通过以上分析得出,masonry不能直接更新xib上的约束。如果我们用mas_updateConstraints方法,masonry会新增一个约束,有可能会导致约束冲突,控制台打印警报,视图布局错乱。
那怎么去更新xib上的约束的。有两个方案,一是脱出来约束直接修改值。二是改变xib上的对应约束优先级,把优先级调低。然后用masonry去添加更高优先级的约束即可。相比较而言觉得方式一更好些。因为更改优先级其实就是要废掉xib上的约束,不够直接麻烦。
疑问
一开始的两个小实验,因为masonry不能直接更新xib上的约束,导致mas_updateConstraints方法实际上是新增了一个最高优先级的约束,导致约束冲突,控制台打印警报。这可以解释通。
但是为什么同样都是约束冲突,尺寸的约束冲突视图界面会用xib的约束。位置的约束冲突视图界面则用masonry的约束让人费解。还有待探索。