一探 mas_updateConstraints 究竟

栏目: IOS · 发布时间: 5年前

内容简介:Masonry 的链式编程对 iOS UI 添加约束简直好用的不得了,想必在使用上大家也都早已烂熟于心。只是对我来讲很早之前就有个更新约束的问题要好好搞搞清楚,就是题目里的当前环境:Xcode 10.1、Simulator iPhone XS 12.1、Masonry 1.1.0首先来看一段布局代码:topView 和 bottomView 上下排列、左右对齐,topView 和 rightView 左右排列、上下对齐。

Masonry 的链式编程对 iOS UI 添加约束简直好用的不得了,想必在使用上大家也都早已烂熟于心。只是对我来讲很早之前就有个更新约束的问题要好好搞搞清楚,就是题目里的 mas_updateConstraints: 方法。在 View 初始化时会添加一系列约束控制布局,而随时更改约束来移动位置也是日常需求,但其中关于这个方法的某些设计并不是直观想象的那样,现在,终于有时间写篇博客一探究竟。

当前环境:Xcode 10.1、Simulator iPhone XS 12.1、Masonry 1.1.0

探索

首先来看一段布局代码:topView 和 bottomView 上下排列、左右对齐,topView 和 rightView 左右排列、上下对齐。

[self.topView mas_makeConstraints:^(MASConstraintMaker *make) {
   make.centerY.equalTo(self.view);
   make.left.equalTo(self.view).offset(50);
   make.size.mas_equalTo(CGSizeMake(100, 30));
}];
[self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
   make.top.equalTo(self.topView.mas_bottom).offset(50);
   make.left.equalTo(self.topView);
   make.size.mas_equalTo(self.topView);
}];
[self.rightView mas_makeConstraints:^(MASConstraintMaker *make) {
   make.top.equalTo(self.topView);
   make.right.equalTo(self.view).offset(-50);
   make.size.mas_equalTo(self.topView);
}];

从12行可以看出 rightView 的 top 依赖于 topView,现在有个需求是将 rightView 与 bottomView 顶部对齐。记得我第一次遇到这种问题的时候想当然的将 rightView 的 top 依赖于 bottomView 了:

[self.rightView mas_updateConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(self.bottomView);
}];

结果是报错、崩溃还是页面错落我倒是忘记了,总之是不能满足需求。当前在 Xcode 10.1、Simulator iPhone XS 12.1 环境下我试了下是页面错落。所以我将代码改成了这样:

[self.rightView mas_updateConstraints:^(MASConstraintMaker *make) {
   make.top.equalTo(self.topView).offset(80);
}];

这里当然可以使用 mas_remakeConstraints: 方法,只不过该方法的意思是清空所有约束重新添加(下文会有源码体现),相比 mas_updateConstraints: 性能低了不少,况且项目开发中往往全部约束散落在各处,难免会有遗漏或者约束错乱的情况。所以我的做法是在 mas_updateConstraints: 方法里不更改约束依赖的对象,而通过计算出一个合适的偏移量来更改 offset 值。所以得出了一个结论:

Masonry 的 mas_updateConstraints: 方法不能更改约束依赖的对象,可以通过计算偏移量来更新布局。

但这究竟是为什么呢?让我们来看一下 Masonry 的 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];
}

constraintMaker 的 updateExisting 属性设置为 YES 之后执行了 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;
}

这段代码 mas_remakeConstraints: 方法也会调用,只不过是将 removeExisting 属性设为 YES,第2行的 if 判断正是上文提到的清空所有约束。后半段代码则是调用 install 方法更新约束,该方法则是调用自 MASConstraint 的子类:MASViewConstraint。下边是关键实现代码:

    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 *)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;
}

逻辑很简单,根据 updateExisting,也就是之前的标识更新布局的赋值来寻找已存在的约束,如果约束存在则执行更新操作,如果约束不存在则当成一条新的约束添加给 View。

关键就在于 layoutConstraintSimilarTo: 查找约束时的判断方法,22~29行,竟然是根据这些条件来判断,这就是为什么在 mas_updateConstraints: 方法里更改约束对象后造成页面错乱的原因,因为它找不到这条约束就把它当成一条新的约束添加到 View 上,导致约束冲突。

从这一点出发的话那我们首先想到的解决这个问题的方案就不再是更改偏移量了,而是通过设置约束的优先级来解决约束冲突的问题。

而且,如果遇到 View 出现重复约束时,比如:

make.top.equalTo(self.topView).offset(10);
make.top.equalTo(self.topView).offset(20);

仅仅通过在 mas_updateConstraints: 方法里更改某条约束的偏移量并不能起到精确控制的作用。所以这种情况下设置优先级也许是个更好的解决方案。关于设置优先级值得注意的有:

  • 不特殊设置时约束的优先级默认是 UILayoutPriorityRequired,也就是最高的
  • 优先级的设值范围在 0~1000 之间,超出这个范围则会崩溃:
    2019-01-18 13:00:09.449167+0800 MasonryTest[54681:15704474] *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘It’s illegal to set priority:1200. Priorities must be greater than 0 and less or equal to NSLayoutPriorityRequired, which is 1000.000000.’
    

所以我们应该将想要更新的约束提前设置一个较低的优先级,再在 mas_updateConstraints: 方法里更新约束并对新的约束设置一个高于原来约束的优先级,且低于 1000。例如:

[self.rightView mas_makeConstraints:^(MASConstraintMaker *make) {
   make.top.equalTo(self.topView).priorityLow();
}];
    
[self.rightView mas_updateConstraints:^(MASConstraintMaker *make) {
   make.top.equalTo(self.bottomView).priorityHigh();
   // or
   // make.top.equalTo(self.bottomView).priority(800);
}];

最后,认识几种优先级类型:

// 1000
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
// 750
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
// 500
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
// 250
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
// 50
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;

以上所述就是小编给大家介绍的《一探 mas_updateConstraints 究竟》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

结网@改变世界的互联网产品经理

结网@改变世界的互联网产品经理

王坚 / 人民邮电出版社 / 2013-5-1 / 69.00元

《结网@改变世界的互联网产品经理(修订版)》以创建、发布、推广互联网产品为主线,描述了互联网产品经理的工作内容,以及应对每一部分工作所需的方法和工具。产品经理的工作是围绕用户及具体任务展开的,《结网@改变世界的互联网产品经理(修订版)》给出的丰富案例以及透彻的分析道出了从发现用户到最终满足用户这一过程背后的玄机。新版修改了之前版本中不成熟的地方,强化了章节之间的衔接,解决了前两版中部分章节过于孤立......一起来看看 《结网@改变世界的互联网产品经理》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具