Masonry是一个轻量级的布局框架,但是代码中使用了大量的block和链式编程的技巧,所以下面简单介绍链接语法以及block。
1. Block的常见用法
- 声明block类型变量
int (^test)(int) = ^(int a) {
return a + 1;
};
- block作为函数参数
- (void)test :(void (^)(NSString *a)) block {
}
- block作为函数返回值
- (void (^)(NSString *))block {
return ^(NSString *a) {
NSLog(@"%@",a);
};
}
- 有时候我们也可以使用typedef来简化block
typedef void(^block)(NSString *a);
- (block)block {
return ^(NSString *a) {
NSLog(@"%@",a);
};
}
- (void)test :(block)block {
}
2. 链式编程思想
很多地方总会将链式编程和函数式编程结合在一起来谈论,这里先简单说明下两者的区别。
- 函数式编程:
将函数作为和其他数据类型一样来进行操作,可以作为其他函数的参数和返回值等,例如:
func1(func2(func3(8)));
- 链式编程
用点的形式连接函数,完成参数的传递和逻辑处理,例如:
make.left.equalTo(self.view.mas_left).with.offset(18);
接着我们来看链式编程是如何实现的,使用人的例子,我们先来看看整体的代码,再逐步分析:
- .h文件
@property (nonatomic,assign) NSUInteger monry;
+ (NSUInteger)life:(void(^)(Person * make))block;
- (Person * (^) (NSUInteger))get;
- (Person * (^) (NSUInteger))use;
- (instancetype)initWithMoney:(NSUInteger)monry;
- (instancetype)initWithGold;
- .m文件
- (instancetype)initWithGold {
if (self = [super init]) {
self.monry = 100;
}
return self;
}
- (instancetype)initWithMoney:(NSUInteger)monry {
if (self = [super init]) {
self.monry = monry;
}
return self;
}
#pragma mark -
+ (NSUInteger)life:(void(^)(Person * make))block {
Person *man = [[Person alloc] init];
block(man);
return man.monry;
}
- (Person * (^) (NSUInteger))get {
return ^(NSUInteger money) {
self.monry += money;
return self;
};
}
- (Person * (^) (NSUInteger))use {
return ^(NSUInteger money) {
self.monry -= money;
return self;
};
}
- 使用
[Person life:^(Person *make) {
make.use(5).get(1);
}];
看.h文件,有一个money属性来存储剩余的钱,
类方法:life
使用了block做参数,block中有一个Person对象
在实现中:先初始化Person对象,该对象作为block参数并且调用block,最后返回刚刚对象的money属性。返回block的方法:get、use
使用了block作为返回值
实现中:操作成员变量money,返回自身(链式编程的核心)链中的方法:man.use(10).get(20);
可以发现在链中的调用都是通过点进行的,但是在OC中都是通过
[object method] 的形式调用的,这里分为两种:
返回值为block的方法:
首先该方法返回值是一个block,调用起来相当于getter方法,所以等价于一个block的属性
@property (nonatomic, readonly) Person * (^get) (NSUInteger);
调用过程可以分为两步:
-
-
(Person * (^)(NSUInteger)) tempBlock = make.get;
这里make.get是获取了属性
-
-
-
tempBlock(4);
返回值为对象的方法: 在OC中,如果符合getter方法的格式(点语法在等号左边时为setter,否则为getter),则可以通过点语法进行调用。
-
3. Masonry基本使用
在传统的initWithRect方法中,设置view的frame所使用的rect由CGRectMake方法创建,该方法由左上角的点以及宽高共四个参数来确定view的位置。在自动布局中,也是需要满足四个条件来确定view的frame。
- 有以下常见属性
@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; //文本基线
常见约束的各种类型
描述 | key |
---|---|
尺寸 | width、height、size |
边界 | left、leading、right、trailing、top、bottom |
中心点 | center、centerX、centerY |
边界 | edges |
偏移量 | offset、insets、sizeOffset、centerOffset |
约束优先级 | priority(0~1000),multipler乘因数, dividedBy除因数 |
- 以下是常用的修改约束的方法
//新增约束(只能新增一次)
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
//更新约束
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
//清除之前所有的约束,并且添加新约束
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
添加约束前必须要先被添加到一个视图中!否则会崩溃。
- Masonry的简单应用
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.mas_left).offset(10);
make.right.equalTo(self.mas_right).offset(-10);
make.top.equalTo(self.mas_top).offset(10);
make.bottom.equalTo(self.mas_bottom).offset(-10);
}];
以上创建了一个上下左右都相距俯视图10个点的view,也可以通过以下方法简化。
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_offset(UIEdgeInsetsMake(10,10,10,10));
}];
- equalTo & mas_equalTo
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
// equalTo仅支持基本类型
// mas_equalTo支持类型转换,支持复杂类型。是对equalTo的封装。支持CGSize、CGPoint、NSNumber、UIEdgeinsets。
- 对一个数组进行约束
/**
* axisType 方向
* fixedSpacing 间隔
* fixedItemLength 长/宽
* leadSpacing 头部间隔
* tailSpacing 尾部间隔
*/
//等间隔排列,拉伸控件的长/宽来适应
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
//等长/宽排列 ,拉伸控件组之间的间隔来适应
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
- Masonry约束易忽略的技术点
使用Masonry不需要设置控件的translatesAutoresizingMaskIntoConstraints属性为false,block内部已经做过处理了;
防止block中的循环引用,使用弱引用(这是错误观点),在这里block属于非逃逸闭包,block内部引用self不会造成循环引用的
- Masonry约束控件出现冲突的问题
当约束冲突发生的时候,我们可以设置view的key来定位是哪个view
redView.mas_key = @"redView";
greenView.mas_key = @"greenView";
blueView.mas_key = @"blueView";
若是觉得这样一个个设置比较繁琐,怎么办呢,Masonry则提供了批量设置的宏MASAttachKeys
MASAttachKeys(redView,greenView,blueView); //一句代码即可全部设置
- 苹果官方建议:添加/更新约束在
updateConstraints
方法内,需要在最后调用[super updateConstraints]
。
// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
//更新约束
[view updateConstraints:^(MASConstraintMaker *make) {
make.top.bottom.left.right.equalTo(self);
}];
[super updateConstraints];//最后必须调用父类的更新约束
}
//若视图基于自动布局的,则需要重写这个方法为YES(默认NO)
+ (BOOL)requiresConstraintBasedLayout{
return YES ;
}
- 使用约束需要注意的地方
-
setNeedsUpdateConstraints
当一个自定义view的某个属性发生改变,并且可能影响到constraint时,需要调用此方法去标记constraints需要在未来的某个点更新,系统然后调用updateConstraints
. -
needsUpdateConstraints
constraint-based layout system使用此返回值去决定是否需要调用updateConstraints
作为正常布局过程的一部分。 -
updateConstraintsIfNeeded
在有setNeedsUpdateConstraints
标记的情况下,立即触发约束更新,自动更新布局。 -
updateConstraints
应该在此处建立或更新约束,最后需要调用[super updateConstraints]
-
setNeedsLayout
标记未来将要重新布局,系统会自动调用layoutSubviews
更新布局。 -
layoutIfNeeded
如果有标记则立即调用layoutSubviews
进行布局
- Auto Layout Process & springs and struts(autoresizingMask)的区别
Auto Layout Process 自动布局过程与使用springs and struts(autoresizingMask)比较,Auto layout在view显示之前,多引入了两个步骤:updating constraints 和laying out views。每一个步骤都依赖于上一个。display依赖layout,而layout依赖updating constraints。 updating constraints->layout->display
- updating constraints,被称为测量阶段,其从下向上(from subview to super view),为下一步layout准备信息。可以通过调用方法setNeedUpdateConstraints去触发此步。constraints的改变也会自动的触发此步。但是,当你自定义view的时候,如果一些改变可能会影响到布局的时候,通常需要自己去通知Auto layout,updateConstraintsIfNeeded。
自定义view的话,通常可以重写updateConstraints方法,在其中可以添加view需要的局部的contraints。 - layout,其从上向下(from super view to subview),此步主要应用上一步的信息去设置view的center和bounds。可以通过调用setNeedsLayout去触发此步骤,此方法不会立即应用layout。如果想要系统立即的更新layout,可以调用layoutIfNeeded。另外,自定义view可以重写方法layoutSubViews来在layout的工程中得到更多的定制化效果。
- display,此步时把view渲染到屏幕上,它与你是否使用Auto layout无关,其操作是从上向下(from super view to subview),通过调用setNeedsDisplay触发,
因为每一步都依赖前一步,因此一个display可能会触发layout,当有任何layout没有被处理的时候,同理,layout可能会触发updating constraints,当constraint system更新改变的时候。
需要注意的是,这三步不是单向的,constraint-based layout是一个迭代的过程,layout过程中,可能去改变constraints,有一次触发updating constraints,进行一轮layout过程。
注意:如果你每一次调用自定义layoutSubviews都会导致另一个布局传递,那么你将会陷入一个无限循环中。