iOS Masonry 约束相关
控件自适应大小
scrollView
目标:让编译器自动推算 contentSize 的高度。
实现:将 contanierView 内部自上而下约束完整。
步骤:
- 1> 外部约束
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.backgroundColor = UIColor.blueColor;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.showsHorizontalScrollIndicator = NO;
// scrollView 默认开启滚动效果
scrollView.alwaysBounceVertical = YES;
[self.view addSubview:scrollView];
[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
UIView *containerView = [[UIView alloc] init];
[scrollView addSubview:containerView];
[containerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(scrollView);
// 注意:要让编译器自动算,所以这里不能设置 height !!!
make.width.equalTo(scrollView);
}];
_containerView = containerView;
// 设置 contentSize 可不设置。
// scrollView.contentSize = containerView.bounds.size;
- 2> 让编译器可以推算出 containerView 的高度
// 1>> 内部约束自上而下,让编译器自己推算高度
[lastView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(containerView).offset(-20);
}];
// 或 2>> 给定 containerView 的高度
[containerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(300);
}];
View
目标:让编译器自动推算父控件高度。
实现:(跟 tableViewCell 高度自适应原理一样)外面的约束自上而下进行,如果编译器可以推算出最终高度,那么它就可以帮我们完成 父控件高度 的自适应工作。
UILabel *content_lb = [UILabel new];
[content_lb setText:@".........."];
content_lb.numberOfLines = 0;
[parentView addSubview:content_lb];
[content_lb mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_offset(5);
make.trailing.mas_offset(-5);
make.top.equalTo(head_iv.mas_bottom).offset(20);
make.bottom.mas_equalTo(-21);
}];
[parentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.leading.trailing.equalTo(self.view);
}];
约束优先级 priority
约束的优先级:两个约束在某种情况下冲突,默认不执行其中优先级较低的约束。
同个属性,不同条件的约束的优先级
1> 需求:
产品名称:name 长度不定(富文本),如图1
产品详情:desc 长度不定(富文本),如图2
容量标签:capacity:
- 在 name 和 desc 内容较少情况下与图片底部在一条线上,如图2
- 在 name 和 desc 内容较多情况下与 desc 相距 15 个单位,如图3



2> 约束实现
[name_lb mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(imageView);
make.leading.equalTo(imageView.mas_trailing).offset(20 *MKScale);
make.trailing.mas_offset(-16.5 *MKScale);
}];
[desc_lb mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.equalTo(name_lb);
make.top.equalTo(name_lb.mas_bottom).offset(5 *MKScale);
}];
[capacity_lb mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.equalTo(name_lb);
make.bottom.mas_equalTo(-20 *MKScale);
make.top.greaterThanOrEqualTo(desc_lb.mas_bottom).offset(15 *MKScale);
make.bottom.equalTo(product_iv).priority(500);
}];
3> 场景分析

关键点:
-
name_lb 和 desc_lb 高度不约束,因为是动态改变的,让编译器自己去处理。
-
capacity_lb 满足:
- 约束a:bottom 与 父视图 bottom 相差 20。
- 约束b:top 与 desc_lb bottom 距离 >= 15。(greaterThanOrEqualTo 产生了一个在某种情况下不确定的值,默认优先级:1000)
- 约束c:bottom 与 imageView 水平。 (手动设置低优先级:500)
-
若 dynamic area 很大,约束b 会规定 space 为 15。此时与 约束c 产生冲突。由于 c 的优先级较低,所以 c 不生效。
-
若 dynamic area 不大,会拉伸 space,使得 space 为一个大于 15 的不确定值,此时并不存在约束冲突,会满足约束c。
在iOS 11中,将 cell 布局好,不手动设置 rowHeight 和 restimatedRowHeight 也可以自动适应。(但在 iOS 10 中不可以)
tableView.rowHeight = UITableViewAutomaticDimension;
tableView.estimatedRowHeight = 200;
控件挤压、拉伸问题

场景:stackView 中有 redView 和 label 两个控件,手动设置 stackView 的宽度,必定会拉伸其中一个子控件。
如何控制拉伸哪个控件:
设置对应约束的优先级 priority,优先级低的会改变。
[imgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(30).priority(10); // imageView 优先级低,会被拉伸 或者直接设置 .priorityLow()
make.height.mas_equalTo(20);
}];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(width).priority(100); // 或者直接设置 .priorityHigh() 要将约束写出来,不能采用自适应的方式
}];
动态更新约束
弱引用约束(推荐)
相当于重新创建约束(不是全部,重新创建的是想要更新的约束),适用各种情况,能立马更新。
@property (nonatomic, weak) MASConstraint *constraint;
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
self.constraint = make.right.equalTo(self).priorityLow();
}];
- (void)changePriority:(BOOL)isHigh {
// 让约束失效(内部调用uninstall,从约束组内移除,由于当前约束是弱引用,没有被其他指针强引用着则会被系统回收)
[self.constraint uninstall];
// 重新创建约束(mas_updateConstraints 会把 block 内的约束添加到约束组内并生效)
[self.nameLabel mas_updateConstraints:^(MASConstraintMaker *make) {
if (isHigh) {
self.constraint = make.right.equalTo(self).priorityHigh();
} else {
self.constraint = make.right.equalTo(self).priorityLow();
}
}];
// 刷新布局
[self layoutIfNeeded];
}
强引用约束(oc不推荐,swift适用)
初始化后就立马修改的话(同一个Runloop循环内)并不会有变化,适用于初始化后晚一些再更新的情况。
oc(不推荐)
@property (nonatomic, strong) MASConstraint *constraint;
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
self.constraint = make.right.equalTo(self).priorityLow();
}];
- (void)changePriority:(BOOL)isHigh {
// 让约束失效(内部调用uninstall,从约束组内移除)
[self.constraint deactivate];
// 重新设置优先级
if (isHigh) {
self.constraint.priorityHigh();
} else {
self.constraint.priorityLow();
}
// 让约束生效(内部调用install,重新添加到约束组内)
[self.constraint activate];
// 刷新布局
[self layoutIfNeeded];
}
swift(适用)
var bottomConstraint: Constraint?
view.snp.makeConstraints { (make) in
// ...
bottomConstraint = make.bottom.equalToSuperview().constraint // 约束的引用
}
// 在某些条件下将其失效,重新设置约束
bottomConstraint?.deactivate()
bottomConstraint = nil
其他使用技巧
等间距约束
/**
* distribute with fixed spacing
*
* @param axisType which axis to distribute items along
* @param fixedSpacing the spacing between each item
* @param leadSpacing the spacing before the first item and the container
* @param tailSpacing the spacing after the last item and the container
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
/**
* distribute with fixed item size
*
* @param axisType which axis to distribute items along
* @param fixedItemLength the fixed length of each item
* @param leadSpacing the spacing before the first item and the container
* @param tailSpacing the spacing after the last item and the container
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
//Ex:
NSMutableArray <UIView *> *views = [NSMutableArray array];
for (int i = 0; i < info.count; i++) {
UIView *view = [self createViewWithInfo:info[i]];
[views addObject:view];
}
// 约束左右、间距
[views mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:18 *MKScale leadSpacing:0 tailSpacing:0];
// 约束上下
[views mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.equalTo(self.pwdView);
}];
批量设置约束
假设有View1,view2,view3三个View,我们想要他们的宽高都等于CGSizeMake(100, 50)。我们可以对他们进行批量设置
NSValue *sizeValue = [NSValue valueWithCGSize:CGSizeMake(100, 50)];
[@[view1,view2,view3] mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(sizeValue);
}];
由于我们还要设置view的top,left等位置约束。那可不可以在设置位置的mas_makeConstraints里面批量设置宽高呢?实际是可以的!
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
(void)make.top.left;
make.size.equalTo(@[view2,view3,sizeValue]);
}];
mas_key
当约束冲突发生的时候,我们经常为找不到是哪个View冲突的而烦恼,这时候我们可以设置View的key,就可以清晰的知道是哪个view了
view1.mas_key = @“view1”;
view2.mas_key = @“view2”;
// Masonry提供了批量设置的宏MASAttachKeys,只需要一句代码即可全部设置:
MASAttachKeys(view1,view2);
不使用 mas 前缀(不推荐)
加mas_前缀主要是在扩展系统类的时候为了避免与原有类冲突,这是Apple推荐的做法。不过目前来说,即使不加mas_前缀,也不会有什么问题。所以Masonry提供了不加mas_前缀的方法,只需要你定义几个宏即可。
1. MAS_SHORTHAND
定义MAS_SHORTHAND宏之后。可以使用UIView,NSArray中不带mas_前缀的makeConstraints,updateConstraints,remakeConstraints。以及UIView中不带mas_前缀的Attribute。
2. MAS_SHORTHAND_GLOBALS
默认的equalTo方法只接受id类型的对象。有时候我们想传入一个CGFloat, CGSize, UIEdgeInsets等。还需要将其转化成NSValue对象,比较麻烦。Masonry也考虑到了这种情况。只需要定义MAS_SHORTHAND_GLOBALS宏。就可以直接对equalTo传入基础类型。Masonry自动转化成NSValue对象
遇到的问题
UI校对与设计稿不一致

现象:虽然是按蓝湖标注来设置字体与间距的,但是效果缺不一致。
原因:蓝湖标注无法考虑字体内边距
解决办法:设置 label 高度为 字号+2 (+2的原因是如果字号与高度 1:1,某些字符会显示不全例如:g j)
,因为加了2个位置,会分摊在原先 label 的上下各1个像素点,所以要修改原先上下约束各-1。
xib 的控件取不到正确宽高
现象:有些时候,子控件需要依据父控件算宽高,如果是 xib 生成的控件(cell),代码取宽高取得是 xib 图形界面中显示的宽高

例如,将cell宽度拉至300,tableView 宽度 = 屏宽,此时在375 的屏下,显示 cell宽度为375,但是取值却还是300
解决办法:需要计算宽高的控件,其父视图用纯代码方式创建,尽量不使用 xib