Masonry is still actively maintained, we are committed to fixing bugs and merging good quality PRs from the wider community. However if you're using Swift in your project, we recommend using SnapKit as it provides better type safety with a simpler API.
-- https://github.com/SnapKit/Masonry
首先來段免責聲明,Masonry現在雖然任在維護中,但是官方現在的精力在其Swift版本的SnapKit。
Masonry提供了一種鏈式(chainable)描述AutoLayout的DSL(Domain Specific Language)。何為鏈式呢?比如
xiaoming.goToScholl().and.playBasketball()
類似這樣,用“.”將各個詞語連接起來達成一項功能的時候,這樣使得表達式(DSL)更接近自然語言。上面的語句字面意思是不是就是"小明去學校打籃球去了",會幾個英語單詞的小學生基本都能讀懂。如果真如Masonry自己描述的一樣,他用這樣一個“chainable”的DSL是否能夠消除AutoLayout的復雜和冗余呢?
1. 趕緊嘗一口
Masonry支持CocoaPods安裝(廢話,這么Popular的項目怎么能不支持CocoaPods)。在Pod文件中加入
pod 'Masonry'
然后執行:
pod install
在代碼中,只要包含“Masonry.h”頭文件就可以了
#import <Masonry/Masonry.h>
來看個例子效果,在我們寫例子程序的時候,經常遇到幾個按鈕加個日志顯示的場景,假設兩個按鈕在同一行,讓后下面一個日志顯示的TextView,這種的三角布局:
@interface ViewController ()
@property (nonatomic, strong) UIView *topLeft;
@property (nonatomic, strong) UIView *topRight;
@property (nonatomic, strong) UIView *bottom;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_topLeft = [UIView new];
[_topLeft setBackgroundColor: [UIColor yellowColor]];
_topRight = [UIView new];
[_topRight setBackgroundColor:[UIColor redColor]];
_bottom = [UIView new];
[_bottom setBackgroundColor:[UIColor blueColor]];
[self.view addSubview:_topLeft];
[self.view addSubview:_topRight];
[self.view addSubview:_bottom];
// AutoLayout with Masonry
[_topLeft mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(_topRight.mas_width);
make.top.equalTo(self.view.mas_top).offset(20);
make.left.equalTo(self.view.mas_left).offset(10);
make.right.equalTo(_topRight.mas_left).offset(-10);
make.bottom.equalTo(_bottom.mas_top).offset(-10);
}];
[_topRight mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).offset(20);
make.right.equalTo(self.view.mas_right).offset(-10);
make.bottom.equalTo(_bottom.mas_top).offset(-10);
}];
[_bottom mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(_topLeft.mas_height);
make.left.equalTo(self.view.mas_left).offset(10);
make.right.equalTo(self.view.mas_right).offset(-10);
make.bottom.equalTo(self.view.mas_bottom).offset(-10);
}];
}
運行后效果如下:
2.使用Masonry進行布局
看上面的代碼,會發現和以往的frame+center布局的不同的是,這里不在使用initWithFrame
,然后多出來一個函數調用,有且只有這一個函數調用:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
正常來說,基本情況下使用這個一個函數就夠了。Masonry還提供了另外兩個- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
和- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
描述布局DSL作為補充,三者的原理是一樣的,只是不同函數用在不同的上下文,所以只要了解上面這個函數的時候,基本上就掌握了Masonry的DSL了。
** 這里有個需要注意的點,mas_makeConstraints
調用中影響的view必須是以及被addSubview
,也就是必須有parentView **
先來看這個函數原型,就只有一個block參數,block內容會被及時的回調,所以這個函數其實也就是為了調用這個block而準備的,而block里面通過MASConstraintMaker
作為載體來執行我們上面說的“鏈式”DSL,進行布局動作。所以可以認為上面的:
[_topRight mas_makeConstraints:^(MASConstraintMaker *make) {
//make.xxx.xxx().xxx()
}];
是一個模板,填充里面make打頭的DSL即可,這里make就可以認為是要布局的view的代理。對應的,如果要修改一個已經被布局的view的布局策略(只更新新的策略,原有未描述的策略保留),則使用個- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
一樣的去填充“make”開頭的DSL即可?;蛘咄耆匦麻_始布局 - (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
,先清除所有布局,然后再加上新布局條件。
了解了這個模板以后,再來看下模板里面變個內容:
make.width.equalTo(_topRight.mas_width);
基本格式是:
make.attr.constrains
這里以"make"開頭,然后指定其的一個屬性(attr),然后再試這個屬性的約束(constrains)。約束可能是多級的組合,比如.equalTo(self.view.mas_top).offset(20)
的兩級組合,顯示找到父view的top位置,再向下(Y軸)移動20點。
3.布局中的屬性
上面的attr屬性主要有如下幾種:
名稱 | autolayout 對應屬性 | 作用 |
---|---|---|
left | NSLayoutAttributeLeft | 左邊框 |
top | NSLayoutAttributeTop | 上邊框 |
right | NSLayoutAttributeRight | 右邊框 |
bottom | NSLayoutAttributeBottom | 下邊框 |
leading | NSLayoutAttributeLeading | 正常情況下等同于left |
trailing | NSLayoutAttributeTrailing | 正常情況下等同于right |
centerX | NSLayoutAttributeCenterX | center的X坐標 |
centerY | NSLayoutAttributeCenterY | center的Y坐標 |
baseline | NSLayoutAttributeBaseline | 對齊基線 |
leftMargin | NSLayoutAttributeLeftMargin | 左邊的Margin |
rightMargin | NSLayoutAttributeRightMargin | 右邊的Margin |
topMargin | NSLayoutAttributeTopMargin | 頂部的Margin |
bottomMargin | NSLayoutAttributeBottomMargin | 底部的Margin |
leadingMargin | NSLayoutAttributeLeadingMargin | 前導(基本等于left)Margin |
trailingMargin | NSLayoutAttributeTrailingMargin | 后尾(基本等于tail)Margin |
centerXWithinMargins | NSLayoutAttributeCenterXWithinMargins | 中心X坐標Margin |
centerYWithinMargins | NSLayoutAttributeCenterYWithinMargins | 中心Y坐標Margin |
width | NSLayoutAttributeWidth | 寬度 |
height | NSLayoutAttributeHeight | 高度 |
注意,這里雖然說屬性,但其其實不是屬性,而是一個Block定義,為了和調用時候保值一直,這里就說他們是屬性了。
這里的屬性其實沒有什么新鮮的,和IB中調storyboard里面的“Alignment Constraints”面板里面的前七項是完全一致的,只是多了兩個新的屬性"width"和“?height”分別表示“寬度”和高度。
另外Margin也有對應的屬性導出。
4.布局中的約束
上面的屬性其實也不是屬性,而是一個Block,其返回值都是MASConstraint *
。而每個“MASConstraint”都有一些約束動作,其也是一個Block并且返回值任是一個MASConstraint *
因此就可以用"."號進行連接類似left.equalTo(self.view.mas_left).offset(10);
進行鏈式操作了。
下面為了符合調用時表現,將Block寫成不帶返回值的C函數原型的形式,而影響的屬性也替換成上表中表示的屬性的Blcok名。
設置偏移
調用方式 | 參數 | 效果 |
---|---|---|
insets(MASEdgeInsets insets) | MASEdgeInsets | 設置view的四邊縮小大小,等于縮放效果 |
sizeOffset(CGSize offset) | CGSize | frame的size相當于參考量的偏移大小 |
centerOffset(CGPoint offset) | CGPoint | center相對于參考量的偏移大小 |
offset(CGFloat offset) | CGFloat | 所指屬性相對于參考量的偏移大小 |
上面的MASEdgeInsets是UIEdgeInsets的typedef:
typedef struct UIEdgeInsets {
CGFloat top, left, bottom, right;
} UIEdgeInsets;
類似于一個CGRect。
另外計算偏移的時候是按照iPhone的屏幕坐標的,也就是往下為Y增長方向,往右為X增長方向,所以上面的例子中(-10)表示靠近右邊框往左10個點或者靠近下邊框網上10個點。
設置優先級
調用方式 | 參數 | 效果 |
---|---|---|
priority | MASLayoutPriority | 其實就是float的UILayoutPriority,設置屬性的優先級 |
priorityLow | 無 | 等于priority(MASLayoutPriorityDefaultLow) |
priorityMedium | 無 | 等于priority(MASLayoutPriorityDefaultMedium) |
priorityHigh | 無 | 等于priority(MASLayoutPriorityDefaultHigh) |
這里Masonry定義了一些priority的常量
typedef UILayoutPriority MASLayoutPriority;
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;
所以后面直接定義了low、medium以及high,在一般場景,分三個等級已經夠用了,不需要再每次記著具體的數值。
關系計算
調用方式 | 參數 | 效果 |
---|---|---|
equalTo(id attr) | CGFloat | 設置屬性等于某個數值 |
greaterThanOrEqualTo((id attr)) | CGFloat | 設置屬性大于或等于某個數值 |
lessThanOrEqualTo(id attr) | CGFloat | 設置屬性小于或等于某個數值 |
multipliedBy(CGFloat multiplier) | CGFloat | 設置屬性乘以因子后的值 |
dividedBy(CGFloat divider) | CGFloat | 設置屬性除以因子后的值 |
連詞
還有兩個特殊的連詞:
- (MASConstraint *)with;
- (MASConstraint *)and;
實際上他們什么也沒有做,只是返回自己本身:
- (MASConstraint *)with {
return self;
}
- (MASConstraint *)and {
return self;
}
但是放到表達式中,卻可以作為連詞讓鏈式表達式更接近自然語言。
5.總結
通過上面的實例程序,和AutoLayout的官方例子一比較也可以看得出Masonry的可讀性更強,在Masonry的例子工程中演示的各種示例向我們展示了其基本能
滿足大部分的布局需求,所以在布局的時候,還是比較推薦用Masonry試一下,如果產品真的有特殊的要求,再行替代也不遲,只是在demo階段,Masonry是首要選擇。
另外一個需要了解的是,當布局不成功的時候,首先請先不要懷疑Masonry,Masonry只是對AutoLayout的一個輕量封裝,先想想AutoLayout的使用約束,是不是
本身的描述就有悖于AutoLayout的設計。這一點可能看文檔還不夠,需要多去實驗才能逐步掌握,希望這邊文章能夠為你的實踐邁出第一步。