Masonry 使用解读
谈主题之前,我们先聊一些相关的方面吧.在app开发过程中我们会遇到各式各样的效果,也会遇到各种各样的开发设备.这些设备之间又有很大的区别,比如,内存大小不同,存储大小不同,屏幕尺寸不同,处理器不同,etc.那我们是怎么去处理这些不同呢,毕竟不可能一个手机型号开发一款app吧.
下面引出我们今天的主题iOS UI适配库 - Masonry.
Masonry 是Robert Payne写的一个关于屏幕适配的OC库,Swift 库为SnapKit.在这里先介绍一下OC的使用吧.
写这篇文章的原因是因为在使用该库的时候发现小组成员使用的有点五花八门的.一方面算是规范,另一方面也算是总结一下吧.
下面我们写下具体的使用总结.
在加载view后采用mas_makeConstraints进行设置布局格式
[self.masLabel1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(100);
make.right.equalTo(self.view).offset(-100);
}];
self.masLabel1.text = @"添加的视图1";
在多视图布局时可以采用
[self.masLabel1 mas_makeConstraints:^(MASConstraintMaker *make) {
// 距离顶部安全区100
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(100);
// 距离屏幕左边100
make.left.equalTo(self.view).offset(100);
// masLabel2、masLabel3的左边距和masLabel1保持一致
make.left.equalTo(@[self.masLabel2,self.masLabel3]);
}];
[self.masLabel2 mas_makeConstraints:^(MASConstraintMaker *make) {
// 和self高度位置中心保持一致
make.centerY.equalTo(self.view);
}];
[self.masLabel3 mas_makeConstraints:^(MASConstraintMaker *make) {
// 距离底部安全区100,bottom往上是负数,往下是正数
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-100);
}];
self.masLabel1.text = @"添加的视图1";
self.masLabel2.text = @"添加的视图2";
self.masLabel3.text = @"添加的视图3";
在视图同行或同列进行布局的时候也可以采用数组.通过对MASAxisType和(withFixedSpacing或withFixedItemLength)进行组合从而实现行列高度固定改变间隔和间隔固定改变高度的操作.
// // 实现masonry水平固定间隔方法
// [self.masonryViewArray mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:30 leadSpacing:10 tailSpacing:10];
// // 设置array的垂直方向的约束
// [self.masonryViewArray mas_makeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(50);
// make.height.mas_equalTo(80);
// }];
// 实现masonry水平固定控件宽度方法
// [self.masonryViewArray mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedItemLength:80 leadSpacing:10 tailSpacing:10];
//
// // 设置array的垂直方向的约束
// [self.masonryViewArray mas_makeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(50);
// make.height.mas_equalTo(80);
// }];
// 实现masonry垂直固定控件高度方法
// [self.masonryViewArray mas_distributeViewsAlongAxis:MASAxisTypeVertical withFixedSpacing:30 leadSpacing:10 tailSpacing:10];
//
// // 设置array的水平方向的约束
// [self.masonryViewArray mas_makeConstraints:^(MASConstraintMaker *make) {
// make.left.equalTo(self.view).offset(50);
// make.width.mas_equalTo(80);
// }];
// 实现masonry垂直方向固定控件高度方法
[self.masonryViewArray mas_distributeViewsAlongAxis:MASAxisTypeVertical withFixedItemLength:80 leadSpacing:10 tailSpacing:10];
// 设置array的水平方向的约束
[self.masonryViewArray mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(50);
make.width.mas_equalTo(80);
}];
根据GitHub文档可以看出,
在视图布局时,采用的是equalTo操作,
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
在视图设置NSNumber数据布局时,采用的是mas_equalTo操作,
Instead of using NSNumber, you can use primitives and structs to build your constraints, like so:
make.top.mas_equalTo(42);
make.height.mas_equalTo(20);
make.size.mas_equalTo(CGSizeMake(50, 100));
make.edges.mas_equalTo(UIEdgeInsetsMake(10, 0, 10, 0));
make.left.mas_equalTo(view).mas_offset(UIEdgeInsetsMake(10, 0, 10, 0));
在设置edges、size、和center时候采用的也是同视图操作的equalTo
// EDGES
// make top, left, bottom, right equal view2
make.edges.equalTo(view2);
// make top = superview.top + 5, left = superview.left + 10,
// bottom = superview.bottom - 15, right = superview.right - 20
make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))
// SIZE
// make width and height greater than or equal to titleLabel
make.size.greaterThanOrEqualTo(titleLabel)
// make width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
// CENTER
// make centerX and centerY = button1
make.center.equalTo(button1)
// make centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
更新视图采用
// this is Apple's recommended place for adding/updating constraints
// this method can get called multiple times in response to setNeedsUpdateConstraints
// which can be called by UIKit internally or in your code if you need to trigger an update to your constraints
- (void)updateConstraints {
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
删除视图时采用
- (void)changeButtonPosition {
[self.button mas_remakeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(self.buttonSize);
if (topLeft) {
make.top.and.left.offset(10);
} else {
make.bottom.and.right.offset(-10);
}
}];
}
需要注意的是在更新试图或者删除视图后,我们需要进行更新操作
@implementation DIYCustomView
- (id)init {
self = [super init];
if (!self) return nil;
// --- Create your views here ---
self.button = [[UIButton alloc] init];
return self;
}
// tell UIKit that you are using AutoLayout
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
// --- remake/update constraints here
[self.button remakeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@(self.buttonSize.width));
make.height.equalTo(@(self.buttonSize.height));
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
- (void)didTapButton:(UIButton *)button {
// --- Do your changes ie change variables that affect your layout etc ---
self.buttonSize = CGSize(200, 200);
// tell constraints they need updating
[self setNeedsUpdateConstraints];
}
equalTo和mas_equalTo
equalTo 是MASConstraint 的一个属性,属于类属性调用.
mas_equalTo则是分为属性和类宏定义两个.
我们在make视图的时候有时候会采用如下写法:
make.top.mas_equalTo(self.view).offset(10);
make.bottom.equalTo(self.view).offset(-100);
虽然都可以实现布局,但是在这里不建议大家这样写.需要坚持视图布局采用equalTo属性,mas_equalTo则是在使用NSNumber的时候去采用.
.equalTo调用分析
通过make调用属性,然后设置的布局,属于文档示范的样式.
.mas_equalTo调用分析
通过make调用宏定义mas_equalTo而非属性mas_equalTo.
再查看宏定义可以看到
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
发现宏定义调用的是equalTo宏定义
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__)
#define offset(...) mas_offset(__VA_ARGS__)
#endif
Masonry 作者在全局速记里面定义了一些常用的写法.我们通过make.的时候发现是只能调用equalTo和mas_equalTo的.所以这里我们可以知道采用视图布局的时候使用equalTo.而且作者也明确说了mas_equalTo是用来处理NSNumber的.那是不是设置所有NSNumber的时候都是采用mas_equalTo.并不是,而是在直接给视图设置固定宽高的时候才会采用mas_equalTo.
另外还有一些常用的属性
insets 设置视图跟父视图或者底部视图的间隔
multipliedBy、dividedBy 设置约束倍数
priority 设置约束优先级,默认优先级为500
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
greaterThanOrEqualTo 约束大于等于
lessThanOrEqualTo 约束小于等于
以及一些常用边界约束
- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;
至于Masonry的一些控制台展示处理,可以参考一下我之前的文章Masonry集锦
至此,Masonry就算彻底的和大家打完招呼了.至于Masonry本身的原理是通过链式调用返回自身实现的,大家有兴趣的可以查看一下.
本文Demo.
知识点
- 三方库
- Masonry