Masonry
Masonry是一个基于AutoLayout
的开源布局框架。类似的还有PureLayout
,snapKit
等。基于 frame的框架有Facade以及Neon。现在我们使用最多的就是Masonry和SnapKit/SnapKit。以下,是对Masonry的使用总结。
核心方法有以下3个:
mas_makeConstraints: 负责添加约束
mas_updateConstraints: 更新约束
mas_remakeConstraints: 清除所有约束
需要注意的是,使用mas_makeConstrains方法的元素必须事先添加到父视图中
按比例取值:
- multipliedBy属性:约束值与该值相乘
- dividedBy属性:约束值与该值相除
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.width.equalTo(self.view.mas_width).dividedBy(2);
make.height.equalTo(self.view.mas_width).multipliedBy(0.5);
}];
dividedBy(2)与multipliedBy(0.5)的值是一样的。最终是一个居中的等宽高的正方形。
Masonry的基本使用-相对父视图布局
1.创建一个View,上下左右空出50个像素。我们可以使用offset
来实现效果:
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).with.offset(50);
make.left.equalTo(self.view.mas_left).with.offset(50);
make.bottom.equalTo(self.view.mas_bottom).with.offset(-50);
make.right.equalTo(self.view.mas_right).with.offset(-50);
}];
也可以不指定约束边,默认取需要添加约束的边,简化写法如下:
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).mas_offset(50);
make.left.equalTo(self.view).mas_offset(50);
make.right.equalTo(self.view).mas_offset(-50);
make.bottom.equalTo(self.view).mas_offset(-50);
}];
还可以再简化些:
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(50)
make.left.equalTo(@50);
make.bottom.equalTo(@(-50));
make.right.equalTo(@(-50));
}];
不声明相对视图,则默认值就是相对于依赖父视图对应相同约束的偏移量。如果你想使用字面量,那么就使用
equalTo(@50)
这种方式。如果不想使用字面量,那么就使用mas_equalTo(50)
的方式。
还可以使用EdgeInsets
来实现:
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(50, 50, 50, 50));
}];
同样的,也可以去除
equalTo(self.view)
,那么也是相对于父视图添加约束。
- 创建一个View,水平居中,垂直居中但是向上偏移50像素。
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(200);
make.height.mas_equalTo(200);
make.center.equalTo(self.view).centerOffset(CGPointMake(0, -100));
//make.center.mas_equalTo(0).centerOffset(CGPointMake(0, -100));
}];
3.有2个 view,topView 是 bottomView 的父视图。现在,我们使用bottomView来撑开父视图topView:
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.right.mas_equalTo(-20);
make.top.mas_equalTo(100);
}];
[_bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.mas_equalTo(50);
make.right.bottom.mas_equalTo(-50);
make.height.mas_equalTo(80);
}];
Masonry的基本使用-相对兄弟视图布局
创建一个topView,水平居中,垂直居中但是向上偏移50像素。创建另一个 view,等宽等高,距离topView的底部100个像素。
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(200);
make.height.mas_equalTo(200);
make.center.equalTo(self.view).centerOffset(CGPointMake(0, -100));
}];
[_bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(_topView);
make.top.equalTo(self.topView.mas_bottom).offset(100);
make.left.and.right.equalTo(_topView);
}];
mas_topLayoutGuide
与mas_bottomLayoutGuide
的使用
mas_topLayoutGuide
主要是针对导航栏布局,mas_bottomLayoutGuide
则针对 tabbar 布局。
如果存在导航,那么我们设置topView 距离顶部为0,那么会发现 topView 会穿透到导航栏下面:
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.right.mas_equalTo(-20);
make.height.mas_equalTo(80);
make.top.mas_equalTo(0);
}];
你可以通过设置导航栏的来解决:
self.navigationController.navigationBar.translucent = NO;
或者手动加上导航栏的高度:
make.top.mas_equalTo(导航栏高度 + 偏移量);
当然,你可以使用mas_topLayoutGuide
来布局:
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.right.mas_equalTo(-20);
make.height.mas_equalTo(80);
make.top.mas_equalTo(self.mas_topLayoutGuide);
}];
同理,如果你想距离 tabbar 进行布局,那么就使用mas_bottomLayoutGuide
来进行布局。
masonry 中数组的使用
假如一个 view 的高度设置的是数组,那么会取数组中高度最低的那个值:
_topView = [UIView new];
_topView.backgroundColor = [UIColor blueColor];
[self.view addSubview:_topView];
_bottomView = [UIView new];
_bottomView.backgroundColor = [UIColor redColor];
[self.view addSubview:_bottomView];
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(0);
make.width.height.mas_equalTo(100);
make.top.mas_equalTo(self.mas_topLayoutGuide);
}];
[_bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(0);
make.width.height.mas_equalTo(150);
make.top.mas_equalTo(_topView.mas_bottom).offset(30);
}];
UIView *testView = [[UIView alloc]init];
testView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:testView];
[testView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(0);
make.width.height.mas_equalTo(@[_topView.mas_height, _bottomView.mas_height]);
make.top.mas_equalTo(_bottomView.mas_bottom).offset(40);
}];
testView 最终取的是100的值。
masonry 布局 UIScrollView
在使用masonry对UIScrollView设置约束时,我们不能直接设置 contentsize,我们需要使用一个 contentView 来撑起 contentSize。
// 水平方向滚动视图
UIScrollView *scrollView = ({
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.backgroundColor = UIColor.orangeColor;
scrollView.pagingEnabled = YES;
scrollView;
});
[self.view addSubview:scrollView];
//设置 Scrollview 的约束
[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.insets(UIEdgeInsetsMake(150, 50, 50, 50));
}];
// 设置scrollView的子视图,即过渡视图contentSize
UIView *contentView = [[UIView alloc] init];
[scrollView addSubview:contentView];
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(scrollView);
make.height.mas_equalTo(scrollView);
}];
UIView *previousView = nil;
for (int i = 0; i < 10; i++) {
UILabel *label = ({
UILabel *label = [[UILabel alloc] init];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 2;
label.backgroundColor = UIColor.redColor;
label.text = [NSString stringWithFormat:@"水平方向\n第 %d 个视图", (i + 1)];
label;
});
// 添加到父视图,并设置过渡视图中子视图的约束
[contentView addSubview:label];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(contentView).offset(20);
make.bottom.equalTo(contentView).offset(-20);
make.width.equalTo(scrollView).offset(-40);
if (previousView) {
make.left.mas_equalTo(previousView.mas_right).offset(40);
} else {
make.left.mas_equalTo(20);
}
}];
previousView = label;
}
//设置将影响到scrollView的contentSize
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(previousView.mas_right).offset(20);
}];
使用 masonry 实现多列等宽
1.不指定每个 view 的宽度,根据视图间距,动态设置 view 的宽度:
self.navigationController.navigationBar.translucent = NO;
NSMutableArray *views = @[].mutableCopy;
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.insets(UIEdgeInsetsMake(50, 50, 50, 50));
}];
for (NSInteger i = 0; i < 3; i++) {
UILabel *label = ({
UILabel *label = [[UILabel alloc] init];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 2;
label.backgroundColor = UIColor.redColor;
label.text = [NSString stringWithFormat:@" %ld", (i + 1)];
label;
});
[views addObject:label];
[_topView addSubview:label];
}
[views mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(_topView);
make.height.mas_equalTo(100);
}];
[views mas_distributeViewsAlongAxis:MASAxisTypeHorizontal
withFixedSpacing:10
leadSpacing:50
tailSpacing:100];
2.指定视图宽度,设置“头部”和“尾部”的间隔,中间间距自动计算:
_topView = [UIView new];
_topView.backgroundColor = [UIColor blueColor];
[self.view addSubview:_topView];
self.navigationController.navigationBar.translucent = NO;
NSMutableArray *views = @[].mutableCopy;
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.insets(UIEdgeInsetsMake(50, 50, 50, 50));
}];
for (NSInteger i = 0; i < 3; i++) {
UILabel *label = ({
UILabel *label = [[UILabel alloc] init];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 2;
label.backgroundColor = UIColor.redColor;
label.text = [NSString stringWithFormat:@" %ld", (i + 1)];
label;
});
[views addObject:label];
[_topView addSubview:label];
}
[views mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(_topView);
make.height.mas_equalTo(70);
}];
[views mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedItemLength:50 leadSpacing:10 tailSpacing:40];
withFixedItemLength:50中,50就是指定的宽度。
2.设置宽高比例:
_topView = [UIView new];
_topView.backgroundColor = [UIColor blueColor];
[self.view addSubview:_topView];
_bottomView = [UIView new];
_bottomView.backgroundColor = [UIColor redColor];
[self.topView addSubview:_bottomView];
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(0);
make.width.height.mas_equalTo(self.view.mas_width).multipliedBy(0.5);
}];
[_bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(0);
make.width.mas_equalTo(_topView);
//subView.height = subView.width * 0.5;
make.height.mas_equalTo(_bottomView.mas_width).multipliedBy(0.5);
}];
3.UILabel 与 UIButton 的自适应:
- (void)createDemo {
self.contentLabel = [[UILabel alloc]init];
self.contentLabel.tag = 100;
[self.view addSubview:self.contentLabel];
self.contentLabel.text = @"最近是用Masonry";
self.contentLabel.backgroundColor = UIColor.redColor;
//设置最大宽度约束
[self.contentLabel setPreferredMaxLayoutWidth:self.view.bounds.size.width - 30];
[self.contentLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self.contentLabel.numberOfLines = 0;
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(100);
make.left.mas_equalTo(15);
}];
UIButton *btn = [[UIButton alloc] init];
self.btn = btn;
btn.backgroundColor = [UIColor blueColor];
btn.titleLabel.numberOfLines = 0;
[btn setTitle:@"添加文字" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(clicked) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.contentLabel.mas_bottom).mas_offset(20);
make.centerX.mas_equalTo(self.view);
make.width.mas_lessThanOrEqualTo(self.view.mas_width).offset(-100);
make.height.mas_greaterThanOrEqualTo(btn.titleLabel.mas_height);
// make.height.mas_equalTo(@[btn.titleLabel.mas_height, btn.imageView.mas_height]);
}];
}
- (void)clicked {
self.contentLabel.text = [self.contentLabel.text stringByAppendingString:@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"];
[self.btn setTitle:[self.btn.titleLabel.text stringByAppendingString:@"BBBBBBBBBBBBBBBB"] forState:UIControlStateNormal];
}
动态修改约束
1.我们可以通过设置优先级来实现约束动画。比如删除一个视图:
_topView = [UIView new];
_topView.backgroundColor = [UIColor blueColor];
[self.view addSubview:_topView];
_bottomView = [UIView new];
_bottomView.backgroundColor = [UIColor redColor];
[self.view addSubview:_bottomView];
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(0);
make.width.height.mas_equalTo(150);
make.top.mas_equalTo(self.mas_topLayoutGuide);
}];
[_bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(0);
make.width.height.mas_equalTo(150);
make.top.mas_equalTo(_topView.mas_bottom).offset(30);
}];
UIView *testView = [[UIView alloc]init];
testView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:testView];
[testView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(0);
make.width.height.mas_equalTo(150);
make.top.mas_equalTo(_bottomView.mas_bottom).offset(40);
make.top.mas_equalTo(_topView.mas_bottom).offset(40).priority(250);
}];
删除视图:
[_bottomView removeFromSuperview];
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
}];
通过卸载和装载约束来实现:
声明2个全局变量:
@property (nonatomic, strong)MASConstraint *constraint; @property (nonatomic, strong)MASConstraint *constraint1;
实现:
UIView *testView = [[UIView alloc]init]; testView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:testView]; _topView = [UIView new]; _topView.backgroundColor = [UIColor blueColor]; [self.view addSubview:_topView]; _bottomView = [UIView new]; _bottomView.backgroundColor = [UIColor redColor]; [self.view addSubview:_bottomView]; [_topView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(0); make.width.height.mas_equalTo(150); make.top.mas_equalTo(self.mas_topLayoutGuide); }]; [_bottomView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(0); make.width.height.mas_equalTo(150); _constraint1 = make.top.mas_equalTo(_topView.mas_bottom).offset(30); make.top.mas_equalTo(testView.mas_bottom).offset(30).priority(250); }]; [testView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(0); make.width.height.mas_equalTo(150); _constraint = make.top.mas_equalTo(_bottomView.mas_bottom).offset(40); make.top.mas_equalTo(_topView.mas_bottom).offset(40).priority(250); }];
点击卸载约束,则发现bottomView 和 testView 的位置发生了交换:
[_constraint uninstall]; [_constraint1 uninstall]; [UIView animateWithDuration:1.0 animations:^{ [self.view layoutIfNeeded]; }];