Masonry

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

調用過程可以分為兩步:

    1. (Person * (^)(NSUInteger)) tempBlock = make.get;這里make.get是獲取了屬性
    1. 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 ; 
}
  • 使用約束需要注意的地方
  1. setNeedsUpdateConstraints
    當一個自定義view的某個屬性發生改變,并且可能影響到constraint時,需要調用此方法去標記constraints需要在未來的某個點更新,系統然后調用updateConstraints.
  2. needsUpdateConstraints
    constraint-based layout system使用此返回值去決定是否需要調用updateConstraints作為正常布局過程的一部分。
  3. updateConstraintsIfNeeded
    在有setNeedsUpdateConstraints標記的情況下,立即觸發約束更新,自動更新布局。
  4. updateConstraints
    應該在此處建立或更新約束,最后需要調用[super updateConstraints]
  5. setNeedsLayout
    標記未來將要重新布局,系統會自動調用layoutSubviews更新布局。
  6. 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

  1. updating constraints,被稱為測量階段,其從下向上(from subview to super view),為下一步layout準備信息。可以通過調用方法setNeedUpdateConstraints去觸發此步。constraints的改變也會自動的觸發此步。但是,當你自定義view的時候,如果一些改變可能會影響到布局的時候,通常需要自己去通知Auto layout,updateConstraintsIfNeeded。
    自定義view的話,通常可以重寫updateConstraints方法,在其中可以添加view需要的局部的contraints。
  2. layout,其從上向下(from super view to subview),此步主要應用上一步的信息去設置view的center和bounds。可以通過調用setNeedsLayout去觸發此步驟,此方法不會立即應用layout。如果想要系統立即的更新layout,可以調用layoutIfNeeded。另外,自定義view可以重寫方法layoutSubViews來在layout的工程中得到更多的定制化效果。
  3. 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都會導致另一個布局傳遞,那么你將會陷入一個無限循環中。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容