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都會導致另一個布局傳遞,那么你將會陷入一個無限循環中。