Masonry的学习
文章目录
- Masonry的学习
- 前言
- 使用Masonry
- Masonry支持的属性
- 修饰语
- 基础API
- Auto Boxing
- 中心点
- 设置边距
- 优先级
- 创建约束
- 更新约束
- 使用Masonry来布局UIScrollview
- 小结
前言
在日常的开发中,我们如果面对一些很复杂的UI布局,我们如果统一使用frame的方式来设置的话,会相当复杂,这时候就孕育而生了自动布局,但是在我们原生的环境下,苹果原生的自动布局语言比较复杂,而我们现在要学习的一个Masonry这个第三方库会给我们提供了更加简洁的语法。
使用Masonry
Masonry支持的属性
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;
属性可以大致分成以下几个部分的内容:
- 尺寸:
width、height、size。
- 边界:
left、leading、right、trailing、top、bottom、edges。
- 中心点:
center、centerX、centerY。
- 偏移量:
offset、insets、sizeOffset、centerOffset。
priority()约束优先级(0~1000),multipler乘因数,dividedBy除因数。
这里给出一张图片便于理解这些相关属性的内容。
修饰语
Masonry为了让代码使用和阅读更容易理解,Masonry还添加了and
和with
两个方法,这两个方法内部只是将self返回,没有任何实际作用,仅仅只是为了方便阅读。
make.top.and.bottom.equalTo(self.containerView).with.offset(padding);
这些修饰符的返回。
- (MASConstraint *)with {
return self;
}
基础API
下面列出了这里常用的一些基础的API
mas_makeConstraints() //添加约束
mas_remakeConstraints() //移除之前的约束,重新添加新的约束
mas_updateConstraints() //更新约束
equalTo() //参数是对象类型,一般是视图对象或者 mas_width 这样的坐标系对象
mas_equalTo() //和上面功能相同,参数可以传递基础数据类型对象,可以理解为比上面的API更强大
width() //用来表示宽度,例如代表view的宽度
mas_width() //用来获取宽度的值。和上面的区别在于,一个代表某个坐标系对象,一个用来获取坐标系对象的值
Auto Boxing
在上面可以看到我们上面有一些API的功能其实类似,只不过有前缀mas_之分,在实际开发中我们需要注意一下这两个API的差异,但是我们也可以通过两个宏定义来让我们不用区分这两种API的区别。
// 定义这个常量,就可以不用在开发过程中使用mas_前缀。
#define MAS_SHORTHAND
// 定义这个常量,就可以让Masonry帮我们自动把基础数据类型的数据,自动装箱为对象类型。
#define MAS_SHORTHAND_GLOBALS
我们来看一下下面这段代码来解释一下这个Auto Boxing的一个简单过程
UIView* view = [[UIView alloc] init];// 创建一个view
view.backgroundColor = UIColor.redColor;
[self.view addSubview:view];// 添加到父亲视图
UIEdgeInsets pddings = UIEdgeInsetsMake(20, 20, 20, 20);
[view makeConstraints:^(MASConstraintMaker *make) {// 添加Masonry约束 注意一定要在我们添加到父亲视图之后进行一个添加
// 设置宽度和高度为100
// euqalTo(@100),这里不用mas前缀的话,参数要加@.
// 如果我们想要直接使用数字的话,Masonry提供了mas_前缀。
// 这个前缀会自动将基础数据类型转换为NSNumber类型。
// 这个过程叫做封箱(Auto Boxing)。
// "mas_xx"开头的宏定义,内部都是通过MASBoxValue()函数实现的。
// 这样的宏定义主要有四个,分别是mas_equalTo()、mas_offset()和大于等于、小于等于四个。
// 不过因为引用了开头两个宏定义。所以这里应该不加也没事。
make.height.width.equalTo(100);
}];
这部分代码与注释来源于iOS开发——Masonry的使用。
中心点
[view makeConstraints:^(MASConstraintMaker *make) {// 添加Masonry约束 注意一定要在我们添加到父亲视图之后进行一个添加
make.center.equalTo(self.view);//我们可以通过这种方式将这个视图添加到我们的视图中心点的位置。
}];
设置边距
UIEdgeInsets pddings = UIEdgeInsetsMake(20, 20, 20, 20);
[view makeConstraints:^(MASConstraintMaker *make) {// 添加Masonry约束 注意一定要在我们添加到父亲视图之后进行一个添加
//这里需要注意如果设置过宽度和高度,那么就意味着下面可以生效的仅仅之后左侧边距和上方的边距可以生效。
make.top.equalTo(self.view.mas_top).with.offset(pddings.top);
make.left.equalTo(self.view.mas_left).with.offset(pddings.left);
//注意如果是数值的方式来设置的话,我们记得将右下的数值改成负值。
make.right.equalTo(self.view.mas_right).with.offset(-pddings.right);
make.bottom.equalTo(self.view.mas_bottom).with.offset(-pddings.bottom);
//上面代码等同于下面这一行的代码
make.edges.equalTo(self.view).insets(pddings);
}];
优先级
priority
可以指定一个确切的优先级值,范围在0 ~ 1000,值越大优先级越高。priorityHigh
相当于UILayoutPriorityDefaultHigh
,优先级值为750。priorityMedium
介于高优先级和低优先级之间,优先级值在250 ~ 750之间。priorityLow
相当于UILayoutPriorityDefaultLow
,优先级值为250。
优先级写在约束链的最后方
make.top.equalTo(self.view).with.priority(800);
make.bottom.equalTo(self.view).priorityMedium();
make.left.equalTo(self.view).priorityLow();
make.right.equalTo(self.view).priorityHigh();
创建约束
自动布局允许将宽度和长度设置为一个固定值,例如:
[view makeConstraints:^(MASConstraintMaker *make) {
make.height.width.equalTo(100);
}];
但是,自动布局不允许将对齐属性(如left
、right
、centerY
等)设置为常量值。如果为这些属性传递了一个NSNumber类型的值,Masonry会将这些变为相对于视图的父视图的约束,即:
[self.greenView makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(@10);
}];
=>上面的代码会被转化成下方的代码
[self.greenView makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).offset(10);
}];
更新约束
[控件 mas_remakeConstraints:^(MASConstraintMaker *make) {
//这个方法会将以前的约束全部删除,添加新的约束
}];
[控件 mas_updateConstraints:^(MASConstraintMaker *make) {
//这个方法将会覆盖以前的某些特定的约束
}];
我们这里采用第二个方式来进行一个更新,这里笔者给出一个比较简单的案例
- (void)viewDidLoad {
[super viewDidLoad];
self.view1 = [[UIView alloc] init];
UIView* vc = [[UIView alloc] init];
vc.backgroundColor = UIColor.greenColor;
_view1.backgroundColor = UIColor.redColor;
[self.view addSubview:_view1];
UIEdgeInsets pddings = UIEdgeInsetsMake(50, 50, 50, 50);
[_view1 makeConstraints:^(MASConstraintMaker *make) {
//make.height.width.equalTo(100);
//make.center.equalTo(self.view);
make.edges.equalTo(self.view).insets(pddings);
}];
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(press:)];
tap.numberOfTapsRequired = 1;
tap.numberOfTouchesRequired = 1;
[_view1 addGestureRecognizer:tap];
[_view1 addSubview:vc];
[vc makeConstraints:^(MASConstraintMaker *make) {
make.height.width.equalTo(40);
}];
// Do any additional setup after loading the view.
}
-(void)press:(UIGestureRecognizer*)tap {
if (flag) {
UIEdgeInsets pddings = UIEdgeInsetsMake(10, 10, 10, 10);
[_view1 updateConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).insets(pddings);
}];
flag = NO;
} else {
UIEdgeInsets pddings = UIEdgeInsetsMake(50, 50, 50, 50);
[_view1 updateConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).insets(pddings);
}];
flag = YES;
}
}
下面是一个实现的效果。
这里简单总结一下有关约束的问题:
mas_makeConstraints
只负责新增约束,每次调用会创建新的约束,如果对已存在的约束再次创建,会提示冲突(此时会存在对同一属性的两条约束)。mas_updateConstraints
用于更新之前创建的约束,如果对已存在的约束进行更新,没有任何问题(此时只存在对同一属性的最新一条约束)。mas_remakeConstraints
会清除之前的所有约束,仅保留最新的约束。
使用Masonry来布局UIScrollview
对UIScrollView使用自动布局的时候,经常会遇到很多问题,比如布局无效,不能滑动等。主要是因为UIScrollView除了有自己的frame之外,还有一个contentSize。如果对子视图进行布局时参考了scrollView,例如设置子视图的leading/trailing/top/bottom属性等于scrollView的leading/trailing/top/bottom,这实际上是子视图相对于scrollview的contentsize来确定的,而非bounds。而且由于scrollview的contentSize又是根据子视图的位置决定的,这样就形成了一个依赖循环。iOS自动布局内容选自这里
所以我们该怎么使用Masonry来布局UIScrollview,这里我们最重要的内容就是把我们内部的视图布局与UIScrollView的关系剥离开,这里我们就可以使用一个过渡的视图,将过渡视图和原先的UIScrollView重叠,然后对于UIScrollVIew内部的视图全部都布局在这个过渡视图中间。
- 子视图不能依赖任何scrollView有关的布局,即不能参考scrollView的位置。
- 子视图除了要确定自己的大小以外,还需要确定自己与contentSize四周的距离,以此来确定contentSize。
这里我们开始通过仿写一下照片墙居中的一个小demo来尝试使用MAsonry来布局一下UIscrollView,下面先给出代码。
我们先确定我们UIScrollView的布局
[scrollView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(UIEdgeInsetsMake(0, 0, 0, 0));//布局UIScrollView
}];
然后设置一个过渡视图,来确定UIScrollView的ContentSize的大小
[_contentView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(scrollView);//确定ContentSize的大小
make.width.equalTo(scrollView);//竖向滑动固定宽度,横向滑动固定高度
}];
然后我们就可以把所有的原本需要添加到UIScrollView的视图全部添加到现在的过渡视图中
viewInsert.backgroundColor = UIColor.redColor;
viewInsert.tag = 100 + i;
//UIEdgeInsets pddings = UIEdgeInsetsMake(50, 50, 50, 50);
[viewInsert makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(i / 3 * 140 + 70);
make.left.equalTo(i % 3 * 120 + 20);
make.width.equalTo(100);
make.height.equalTo(100);
}];
最后我们还需要通过最后一个视图来确定我们过渡视图的底部的位置
[_contentView makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(previousView);
}];
最后这是完整的代码
- (void)viewDidLoad {
[super viewDidLoad];
//self.view1 = [[UIView alloc] init];
UIScrollView* scrollView = [[UIScrollView alloc] init];
scrollView.backgroundColor = UIColor.orangeColor;
[self.view addSubview: scrollView];
[scrollView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(UIEdgeInsetsMake(0, 0, 0, 0));//布局UIScrollView
}];
self.contentView = [[UIView alloc] init];
_contentView.backgroundColor = UIColor.clearColor;
[scrollView addSubview:_contentView];
[_contentView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(scrollView);//确定ContentSize的大小
make.width.equalTo(scrollView);//竖向滑动固定宽度,横向滑动固定高度
}];
UIButton* previousView = [UIButton buttonWithType:UIButtonTypeCustom];
for (int i = 0; i < 9; i++) {
UIButton* viewInsert = [UIButton buttonWithType:UIButtonTypeCustom];
NSString* str = [NSString stringWithFormat:@"%d.jpg", i + 1];
UIImage* image = [UIImage imageNamed:str];
[viewInsert setImage:image forState:UIControlStateNormal];
[viewInsert setImage:image forState:UIControlStateSelected];
if (flag == YES) {
NSLog(@"11");
[viewInsert addTarget:self action:@selector(press:) forControlEvents:UIControlEventTouchUpInside];
}
//UIView* viewInsert = [[UIView alloc] init];
[self.contentView addSubview:viewInsert];
viewInsert.backgroundColor = UIColor.redColor;
viewInsert.tag = 100 + i;
//UIEdgeInsets pddings = UIEdgeInsetsMake(50, 50, 50, 50);
[viewInsert makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(i / 3 * 140 + 70);
make.left.equalTo(i % 3 * 120 + 20);
make.width.equalTo(100);
make.height.equalTo(100);
}];
previousView = viewInsert;
}
[_contentView makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(previousView);
}];
-(void)press:(UIButton*)btn {
if (flag && !btn.selected) {
btn.selected = YES;
[self.contentView bringSubviewToFront:btn];
[btn remakeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.height.and.width.equalTo(300);
}];
flag = NO;
} else if (!flag && btn.selected) {
NSLog(@"22");
btn.selected = NO;
NSInteger i = (btn.tag - 100) / 3;
NSLog(@"%ld", i);
NSInteger j = (btn.tag - 100) % 3;
[btn remakeConstraints:^(MASConstraintMaker *make) {
make.height.and.width.equalTo(100);
make.top.equalTo(i * 140 + 70);
make.left.equalTo(j * 120 + 20);
}];
flag = YES;
} else {
;
}
}
@end
最后实现效果:
小结
-
对视图使用自动布局之前,需要先将视图添加到父视图上。
-
使用Masonry时注意区分
mas_equalTo
和equalTo
的区别:mas_equalTo
- 是对equalTo的封装,支持NSNumber、CGSize、CGPoint、UIEdgeInsets;
-
mas_equalTo是一个Macro,比较的是值;
-
一般对于数值元素使用mas_equalTo,例如
make.width.mas_equalTo(100);
。 -
equalTo
- 只支持基本类型;
- equalTo比较的是view;
- 对于对象或多个属性的处理使用equalTo,例如
make.left.and.right.equalTo(self.view);
。
-
对视图使用自动布局后,如果尝试获取它的frame很可能获取到的是0。对于这种情况,我们可以在获取视图frame之前手动调用
layoutIfNeeded
方法来更新布局。 -
Masonry中的block不存在循环引用的问题,不必使用weakSelf。虽然block内引用了view,但block并没有被view所持有,因此不会发生循环引用。
笔者认为这段总结很好,摘自iOS开发—Masonry
这是笔者第一次接触MAsonry这个第三方库以及相关的一些使用,之后会继续学习相关内容。