使用Masonry的mas_updateConstraints错误理解

本文深入探讨了Masonry布局库中的mas_updateConstraints方法,并通过具体示例解释了如何正确使用此方法来更新视图约束,避免布局冲突的问题。

Masonry就不做过多的介绍了,搞iOS布局的应该都知道这个开源库,使用它能节省不少体力,最近在项目中使用这个库的mas_updateConstraints时,发现该方法和自己想象的有点不一样。先贴下自己的代码:

# BaseClass
 [_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
    self.textLabelLeftLayout = make.left.equalTo(self.checkedButton.mas_right);
    make.centerY.equalTo(self.mas_centerY);
    make.height.mas_equalTo(checkBoxWidth);
    make.right.lessThanOrEqualTo(self.mas_right);
}];

这是基类里对textlabel的布局,其相对view本身居中显示,而子类里想改变这种布局方式,改成和另外一个button对齐显示,因此我就在子类里调整布局如下:

# SubClass
  [self.textLabel mas_updateConstraints:^(MASConstraintMaker *make) {
      make.centerY.equalTo(self.checkedButton.mas_centerY);
  }];

本以为这么做Masonry会帮我把centerY的布局更新,但是编译运行时会提示存在冲突,有两个centerY布局约束,不知道该使用哪一个,然后我就读了下Masonry的源码,发现原来mas_updateConstraints方法里对同一个布局的理解就是相对的元素也是一致才行,即这里这样做才算一次update:

# SubClass
  [self.textLabel mas_updateConstraints:^(MASConstraintMaker *make) {
      make.centerY.equalTo(self.mas_centerY).offset(10);
  }];

所以,这里的一个update是针对如下这种情形才行:
A->B A->B的变化
A->C 这里是一个新的约束
源码里有个方法是对是否是同一个约束的判断:

- (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;
}

可以看到,需要firstItem,secondItem,firstAttribute,secondAttribute,relation,multiplier,priority等一致才会当做同一个约束,否则都不算做一个约束。所以在使用mas_updateConstraints时大家一定要分清楚是否update还是重新添加了一个约束。


PS:针对我遇到的这种使用情况,我在基类里将我的居中约束设置了一个优先级来处理的

# BaseClass
 [_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
    self.textLabelLeftLayout = make.left.equalTo(self.checkedButton.mas_right);
    make.centerY.equalTo(self.mas_centerY).priorityMedium();
    make.height.mas_equalTo(checkBoxWidth);
    make.right.lessThanOrEqualTo(self.mas_right);
}];
<think>我们参考用户提供的引用内容,特别是引用[1]和[2]中提到的关于`mas_topLayoutGuide`和`mas_bottomLayoutGuide`的信息。用户的问题是如何在使用`make.bottom.mas_equalTo(self.view.mas_bottom)`时,避免内容被tabbar覆盖,即留出tabbar的区间。 根据引用[1]和[2]: - `self.mas_topLayoutGuide`和`self.mas_bottomLayoutGuide`是用于自动适配导航栏和tabbar的。 - 具体来说,`self.mas_bottomLayoutGuide`表示tabbar或工具栏的顶部位置(当存在时),而`self.mas_bottomLayoutGuideBottom`表示tabbar的底部位置(也就是视图底部减去tabbar高度,如果tabbar存在的话)。 - 因此,如果我们希望视图的底部避开tabbar,那么我们应该约束到`self.mas_bottomLayoutGuide`的顶部(即`self.mas_bottomLayoutGuideTop`),或者直接使用`self.mas_bottomLayoutGuideBottom`作为参考。 用户当前使用的是: ```objectivec make.bottom.mas_equalTo(self.view.mas_bottom) ``` 这样会将视图的底部紧贴到整个self.view的底部,如果存在tabbar,那么内容就会被tabbar覆盖。 解决方案: 我们可以将底部约束改为相对于`self.mas_bottomLayoutGuideTop`,这样就会在tabbar的上方。或者,如果我们希望视图底部对齐到tabbar的顶部,那么: ```objectivec make.bottom.mas_equalTo(self.mas_bottomLayoutGuideTop) ``` 但是,根据引用[2]中的说明,也可以使用`self.mas_bottomLayoutGuide`的顶部或底部。具体来说: - `self.mas_bottomLayoutGuideTop`:表示tabbar的顶部位置(当tabbar存在时,这个位置就是屏幕底部减去tabbar高度;不存在时,就是屏幕底部)。 - `self.mas_bottomLayoutGuideBottom`:表示屏幕底部(无论tabbar是否存在)。 因此,如果我们希望视图底部在tabbar上方,那么应该使用`self.mas_bottomLayoutGuideTop`。 所以,修改后的约束可以是: ```objectivec make.bottom.mas_equalTo(self.mas_bottomLayoutGuideTop) ``` 或者,如果我们希望视图底部紧贴屏幕底部(但不会被tabbar覆盖,也就是在tabbar上方),那么也可以使用: ```objectivec make.bottom.mas_equalTo(self.mas_bottomLayoutGuide).offset(0); ``` 但是注意,引用[2]中说明`mas_bottomLayoutGuide`本身是一个id类型,不能直接使用,通常我们使用它的top或bottom属性。所以更推荐使用`self.mas_bottomLayoutGuideTop`。 另外,引用[1]中给出的示例是: ```objectivec make.top.mas_equalTo(self.mas_topLayoutGuide); ``` 这是用于顶部避免导航栏的。同理,底部避免tabbar应该使用`self.mas_bottomLayoutGuide`的顶部。 因此,正确的做法是: ```objectivec make.bottom.equalTo(self.mas_bottomLayoutGuideTop); ``` 但是,这里有一个注意点:`self.mas_bottomLayoutGuideTop`是相对于当前视图控制器的view的。所以,我们需要确保这个约束添加在正确的视图上。 另外,在引用[3]中有一个示例,它设置了一个UILabel的底部约束为: ```objectivec make.bottom.equalTo(self.contentView).with.offset(-10); ``` 如果我们希望避免tabbar,那么我们就不能直接约束到self.view的底部,而是应该约束到`self.mas_bottomLayoutGuideTop`。 总结步骤: 1. 确定当前视图控制器是否包含tabbar(比如是UITabBarController的一个子控制器)。 2. 在添加底部约束时,使用`self.mas_bottomLayoutGuideTop`作为参考点。 示例代码: ```objectivec [subview mas_makeConstraints:^(MASConstraintMaker *make) { // 其他约束... make.bottom.equalTo(self.mas_bottomLayoutGuideTop).offset(-10); // 如果需要底部留出10个点的边距 }]; ``` 这样,subview的底部就会在tabbar的上方10个点的位置(如果存在tabbar)。如果不存在tabbar,那么`self.mas_bottomLayoutGuideTop`就等于self.view的底部,所以效果就是距离self.view底部10个点。 这样我们就实现了底部自适应tabbar的存在与否。 注意:在iOS 11之后,苹果引入了safe area的概念,所以也可以使用safe area来布局。但是根据提供的引用,用户使用的是Masonry,并且提到了`mas_bottomLayoutGuide`,所以上述方法是适用的。 另外,在引用[5]中提到了更新约束的方法,如果需要动态调整,可以使用`mas_updateConstraints`或`mas_remakeConstraints`。 因此,针对用户的问题,答案是:使用`self.mas_bottomLayoutGuideTop`代替`self.view.mas_bottom`。 </think>根据引用内容,在使用 Masonry 布局时避免内容被 TabBar 覆盖的正确方法是使用 `mas_bottomLayoutGuide` 布局参照系。以下是解决方案和代码实现: ### 解决方案 1. **使用 `mas_bottomLayoutGuideTop`** 将底部约束关联到 `self.mas_bottomLayoutGuideTop`(TabBar 顶部),确保内容停留在 TabBar 上方: ```objectivec make.bottom.equalTo(self.mas_bottomLayoutGuideTop); ``` 2. **使用 `mas_bottomLayoutGuide` 偏移** 若需要自定义间距(如 10pt 边距): ```objectivec make.bottom.equalTo(self.mas_bottomLayoutGuide).offset(-10); ``` ### 完整示例 ```objectivec [yourView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.view); make.top.equalTo(self.mas_topLayoutGuideBottom); // 顶部对齐到导航栏底部 make.bottom.equalTo(self.mas_bottomLayoutGuideTop); // 底部对齐到TabBar顶部 }]; ``` ### 关键点说明 1. **自动适应机制** - 存在 TabBar 时:`mas_bottomLayoutGuideTop` = 屏幕高度 - TabBar 高度[^2] - 无 TabBar 时:自动退化为 `self.view` 底部[^2] 2. **与直接使用 `self.view.mas_bottom` 的区别** ```objectivec // 错误:会被TabBar覆盖 make.bottom.equalTo(self.view.mas_bottom); // 正确:自动避开TabBar make.bottom.equalTo(self.mas_bottomLayoutGuideTop); ``` 3. **动画更新约束**(引用[5]) 如需动态调整位置: ```objectivec [yourView mas_updateConstraints:^(MASConstraintMaker *make) { make.bottom.equalTo(self.mas_bottomLayoutGuideTop).offset(-20); }]; [UIView animateWithDuration:0.3 animations:^{ [yourView layoutIfNeeded]; }]; ``` ### 注意事项 - 使用前确保视图控制器嵌入在 `UINavigationController` 或 `UITabBarController` 中 - 对 UILabel 等自适应控件需设置 `preferredMaxLayoutWidth`(引用[3]) - 更新约束优先用 `mas_updateConstraints` 而非 `mas_remakeConstraints`(引用[5])
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值