活用Masonry

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);
    }];
    

}

運行后效果如下:

first_blood

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的設計。這一點可能看文檔還不夠,需要多去實驗才能逐步掌握,希望這邊文章能夠為你的實踐邁出第一步。

參考

  1. Masonry
  2. Auto Layout Guide
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容