一、为什么要学masonry(why)?
目前iOS开发中大多数页面都已经开始使用Interface Builder的方式进行UI开发了,但是在一些变化比较复杂的页面,还是需要通过代码来进行UI开发的。
而且有很多比较老的项目,本身就还在采用纯代码的方式进行开发。
而现在iPhone和iPad屏幕尺寸越来越多,虽然开发者只需要根据屏幕点进行开发,而不需要基于像素点进行UI开发。但如果在项目中根据不同屏幕尺寸进行各种判断,
写死坐标的话,这样开发起来是很吃力的。
所以一般用纯代码开发UI的话,一般都是配合一些自动化布局的框架进行屏幕适配。苹果为我们提供的适配框架有:VFL、UIViewAutoresizing、Auto Layout、
Size Classes等。
其中Auto Layout是使用频率最高的布局框架,但是其也有弊端。就是在使用UILayoutConstraint的时候,会发现代码量很多,而且大多都是重复性的代码,
以至于好多人都不想用这个框架。
后来Github上的出现了基于UILayoutConstraint封装的第三方布局框架Masonry,Masonry使用起来非常方便,本篇文章就详细讲一下Masonry的使用。
二、masonry是什么(what)?
Masonry是一个对系统NSLayoutConstraint进行封装的第三方自动布局框架,采用链式编程的方式提供给开发者API。系统AutoLayout支持的操作,Masonry都支持,
相比系统API功能来说,Masonry是有过之而无不及。
Masonry采取了链式编程的方式,代码理解起来非常清晰易懂,而且写完之后代码量看起来非常少。之前用NSLayoutConstraint写很多代码才能实现的布局,
用Masonry最少一行代码就可以搞定。下面看到Masonry的代码就会发现,太简单易懂了。
Masonry是同时支持Mac和iOS两个平台的,在这两个平台上都可以使用Masonry进行自动布局。我们可以从MASUtilities.h文件中,看到下面的定义,
这就是Masonry通过宏定义的方式,区分两个平台独有的一些关键字。
支持的属性:
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;
三、怎样学习masonry(how)?
1.masonry遇到的坑
在使用Masonry进行约束时,不用设置weak控制器self,因为masonry的内容使用的不是block,不存在循环引用。
在使用Masonry进行约束时,有一些是需要注意的。
在使用Masonry添加约束之前,需要在addSubview之后才能使用,否则会导致崩溃。
在添加约束时初学者经常会出现一些错误,约束出现问题的原因一般就是两种:约束冲突和缺少约束。对于这两种问题,可以通过调试和log排查。
之前使用Interface Builder添加约束,如果约束有错误直接就可以看出来,并且会以红色或者黄色警告体现出来。而Masonry则不会直观的体现出来,而是以运行过程中崩溃或者打印异常log体现,所以这也是手写代码进行AutoLayout的一个缺点。
这个问题只能通过多敲代码,积攒纯代码进行AutoLayout的经验,慢慢就用起来越来越得心应手了。
Masonry 设置UILabel时多行一定要设置宽度。
2.基础API
mas_makeConstraints() 添加约束
mas_remakeConstraints() 移除之前的约束,重新添加新的约束
mas_updateConstraints() 更新约束
equalTo() 参数是对象类型,一般是视图对象或者mas_width这样的坐标系对象
mas_equalTo() 和上面功能相同,参数可以传递基础数据类型对象,可以理解为比上面的API更强大
width() 用来表示宽度,例如代表view的宽度
mas_width() 用来获取宽度的值。和上面的区别在于,一个代表某个坐标系对象,一个用来获取坐标系对象的值
3.修饰语句
Auto Boxing
上面例如equalTo或者width这样的,有时候需要涉及到使用mas_前缀,这在开发中需要注意作区分。
如果在当前类引入#import "Masonry.h"之前,用下面两种宏定义声明一下,就不需要区分mas_前缀。
// 定义这个常量,就可以不用在开发过程中使用"mas_"前缀。
#define MAS_SHORTHAND
// 定义这个常量,就可以让Masonry帮我们自动把基础数据类型的数据,自动装箱为对象类型。
#define MAS_SHORTHAND_GLOBALS
修饰语句
Masonry为了让代码使用和阅读更容易理解,所以直接通过点语法就可以调用,还添加了and和with两个方法。这两个方法内部实际上什么都没干,
只是在内部将self直接返回,功能就是为了更加方便阅读,对代码执行没有实际作用。
例如下面的例子:
make.top.and.bottom.equalTo(self.containerView).with.offset(padding);
3.常用的方法
设置内边距
/**
设置yellow视图和self.view等大,并且有10的内边距。
注意根据UIView的坐标系,下面right和bottom进行了取反。所以不能写成下面这样,否则right、bottom这两个方向会出现问题。
make.edges.equalTo(self.view).with.offset(10);
除了下面例子中的offset()方法,还有针对不同坐标系的centerOffset()、sizeOffset()、valueOffset()之类的方法。
*/
[self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).with.offset(10);
make.top.equalTo(self.view).with.offset(10);
make.right.equalTo(self.view).with.offset(-10);
make.bottom.equalTo(self.view).with.offset(-10);
}];
通过insets简化设置内边距的方式
// 下面的方法和上面例子等价,区别在于使用insets()方法。
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
// 下、右不需要写负号,insets方法中已经为我们做了取反的操作了。
make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));
}];
4.常用例子
4.1 上下排列
[self.view addSubview:self.blueView];
[self.view addSubview:self.yellowView];
[self.view addSubview:self.redView];
//三个等高视图占一个屏幕,关键点inses内边距和高度可以同时等同于多个视图make.height.equalTo(@[self.redView,self.blueView]);
//上下排列核心思想:起点redView视图固定上左右底,中间blueView固定左右底,底部yellowView固定 左右底,高度等高
1.基本用法
CGFloat padding = 10;
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.equalTo(self.view).insets(UIEdgeInsetsMake(padding, padding, 0, padding));
make.bottom.equalTo(self.blueView.mas_top).offset(-10);
}];
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view).insets(UIEdgeInsetsMake(0, padding, 0, padding));
make.bottom.equalTo(self.yellowView.mas_top).offset(-10);
}];
[self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(self.view).insets(UIEdgeInsetsMake(0, padding, padding, padding));
make.height.equalTo(@[self.redView,self.blueView]);
}];
#pragma mark - ?private
- (NSString *)ranomText{
CGFloat length = arc4random()%50 +5;
NSMutableString *str = [[NSMutableString alloc] init];
for (NSInteger i = 0; i <length; i++) {
[str appendString:@"测试一下啦!"];
}
return str;
}
- (UIView *)blueView{
if (!_blueView) {
_blueView = [[UIView alloc] init];
[_blueView setBackgroundColor:[UIColor blueColor]];
}
return _blueView;
}
- (UIView *)yellowView{
if (!_yellowView) {
_yellowView = [[UIView alloc] init];
[_yellowView setBackgroundColor:[UIColor yellowColor]];
}
return _yellowView;
}
- (UIView *)redView{
if (!_redView) {
_redView = [[UIView alloc] init];
[_redView setBackgroundColor:[UIColor redColor]];
}
return _redView;
}
4.2 左右排列
//左左排列核心思想:起点blueView固定中心、左右,高度及等宽,中间redView固定高度,中心及右边,最右边视图等高,右,中心位置
CGFloat padding = 10.0f;
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).offset(padding);
make.centerY.equalTo(self.view.mas_centerY);
make.height.equalTo(@50);
make.width.equalTo(@[self.redView,self.yellowView]);
make.right.equalTo(self.redView.mas_left).with.offset(-padding);
}];
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(@50);
make.right.equalTo(self.yellowView.mas_left).offset(-padding);
make.centerY.equalTo(self.view.mas_centerY);
}];
[self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(@[self.blueView,self.redView]);
make.height.equalTo(@[self.blueView,self.redView]);
make.right.equalTo(self.view.mas_right).offset(-10);
}];
4.3 上下复合排列
//左右下核心思想,首先总体结构是上下所以要设置等高,然后再设置上下左右的间隙,左1设置上左下右及高度,右1设置右下上就可以因为左边已经有约束,下blueView只需要设置左右下及高度
CGFloat padding = 10.0f;
[self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.equalTo(self.view).insets(UIEdgeInsetsMake(padding, padding, 0, 0));
make.right.equalTo(self.redView.mas_left).offset(-padding);
make.bottom.equalTo(self.blueView.mas_top).offset(-padding);
make.width.equalTo(self.redView.mas_width);
}];
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.right.equalTo(self.view).insets(UIEdgeInsetsMake(padding, 0,0,padding));
make.bottom.equalTo(self.blueView.mas_top).offset(-padding);
}];
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view).insets(UIEdgeInsetsMake(0, padding, 0, padding));
make.bottom.equalTo(self.view.mas_bottom);
make.height.equalTo(self.yellowView.mas_height);
}];
只能设置同一控制的,如自身高度等于宽度的3倍,宽度*这个比例
UIView *topView = [[UIView alloc] init];
topView.backgroundColor = [UIColor redColor];
[self.view addSubview:topView];
UIView *topInnerView = [[UIView alloc] init];
topInnerView.backgroundColor = [UIColor greenColor];
[topView addSubview:topInnerView];
UIView *bottomView = [[UIView alloc] init];
bottomView.backgroundColor = [UIColor blackColor];
[self.view addSubview:bottomView];
UIView *bottomInnerView = [[UIView alloc] init];
bottomInnerView.backgroundColor = [UIColor blueColor];
[bottomView addSubview:bottomInnerView];
[topView mas_makeConstraints:^(MASConstraintMaker *make) {~~
make.left.right.top.equalTo(self.view).width.offset(0);
make.height.mas_equalTo(bottomView);
}];
[topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(topView);
make.width.equalTo(topInnerView.mas_height).multipliedBy(3);
make.center.equalTo(topView);
//设置优先级,设置优先级时一定要加(),优先级 Masonry会优先实现优先级高的设定,发生冲突时,放弃优先级低的设定.
make.width.height.mas_equalTo(topView).priorityLow();
make.width.height.lessThanOrEqualTo(topView);
}];
[bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(self.view);
make.top.equalTo(topView.mas_bottom);
}];
[bottomInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.mas_equalTo(bottomView);
make.center.equalTo(bottomView);
make.height.equalTo(bottomInnerView.mas_width).multipliedBy(3);
//设置优先级,lessThanOrEqualTo小于等于,优先级 Masonry会优先实现优先级高的设定,发生冲突时,放弃优先级低的设定.
make.height.width.equalTo(bottomView).priorityLow();
make.height.width.lessThanOrEqualTo(bottomView);
}];
4.5 点击放大,更新约束的使用
/**3.点击放大,知识点,更新约束,优先级,小于等于等
核心1://初始化宽度,高为100,优先级最低
make.width.height.mas_equalTo(100*self.scale).priorityLow();
//最大放大到整个view,小于等于view的宽度和高度
make.width.height.lessThanOrEqualTo(self.view);
核心2:更新约束 ,告诉需要更新,检查是否需要更新,立即更新*/
@property (nonatomic, strong) UIButton *bigButton;
@property (nonatomic, assign) CGFloat scale;//比例
self.scale = 1.0;
self.bigButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.bigButton setTitle:@"点我放大" forState:UIControlStateNormal];
self.bigButton.layer.borderColor = UIColor.greenColor.CGColor;
self.bigButton.layer.borderWidth = 3;
[self.bigButton addTarget:self action:@selector(onGrowButtonTaped:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.bigButton];
[self.bigButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.view);
//初始化宽度,高为100,优先级最低
make.width.height.mas_equalTo(100*self.scale).priorityLow();
//最大放大到整个view
make.width.height.lessThanOrEqualTo(self.view);
}];
#pragma mark - ?event response
- (void)onGrowButtonTaped:(UIButton *)sender{
self.scale += 0.2;
//告诉self.view约束需要更新
[self.view setNeedsUpdateConstraints];
// 调用此方法告诉self.view检测是否需要更新约束,若需要则更新,下面添加动画效果才起作用
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded]; //立即更新
}];
}
#pragma mark - updateViewConstraints
- (void)updateViewConstraints{
[self.bigButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
//设置优先级
make.width.height.mas_equalTo(100*self.scale).priorityLow();
make.width.height.lessThanOrEqualTo(self.view);
}];
//调用父类更新约束
[super updateViewConstraints];
}
4.6 scrollView的简单适配
/**4. scrollView,核心思想就是先固定四周,然后再设置contentSize,在这里是底部
核心1:preferredMaxLayoutWidth 最大的适配宽度
核心2:scrollView的适配,首先先固定,然后设置底部的值
核心3:配置label顶部间距
*/
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.pagingEnabled = NO;
[self.view addSubview:self.scrollView];
[self.scrollView setBackgroundColor:[UIColor lightGrayColor]];
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
UILabel *lastLabel = nil;
for (NSInteger i = 0; i < 20; i++) {
UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 0;
label.layer.borderColor = [UIColor greenColor].CGColor;
label.layer.borderWidth = 2.0;
label.text = [self ranomText];
label.preferredMaxLayoutWidth = screenWidth - 30;
label.textAlignment = NSTextAlignmentLeft;
[self.scrollView addSubview:label];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).offset(15);
make.right.equalTo(self.view.mas_right).offset(-15);
if (lastLabel) {//下一行是第一行最后一个label加上一定的间距
make.top.mas_equalTo(lastLabel.mas_bottom).offset(20);
}else{
make.top.mas_equalTo(self.scrollView.mas_top).offset(20);
}
}];
lastLabel = label;
}
[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
make.bottom.equalTo(lastLabel.mas_bottom).offset(20);
}];
#pragma mark - ?private
- (NSString *)ranomText{
CGFloat length = arc4random()%50 +5;
NSMutableString *str = [[NSMutableString alloc] init];
for (NSInteger i = 0; i <length; i++) {
[str appendString:@"测试一下啦!"];
}
return str;
}