追求Masonry

Autolayout就像一個知情達理,善解人意的好姑娘独悴,可惜長相有點不堪入目蹬昌,所以追求者寥寥無幾混驰。所幸遇到了化妝大師cloudkite,給她來了一個完美的化妝皂贩,從此丑小鴨Autolayout變成了美天鵝Masonry栖榨。前幾日有幸一見,果然名不虛傳明刷,長相甜美婴栽,還善解人意。我果斷放棄了Frame辈末,開始追求Masonry

初識Masonry

初見

我們先來看看Masonry到底有多美愚争。
我要設置一個containView,他距離superView的上下左右邊距都是10本冲。
如果我用frame准脂,應該是這樣寫的:

UIEdgeInsets edge = UIEdgeInsetsMake(10, 10, 10, 10);
CGSize superSize = view.superview.frame.size;
CGFloat width = superSize.width - edge.left - edge.right;
CGFloat heitht = superSize.height - edge.top - edge.bottom;
view.frame = CGRectMake(edge.left, edge.top, width, heitht);

邏輯比較復雜,閱讀的時候還得想半天才能想明白檬洞,這個frame到底要表達的是什么意思狸膏。而且關鍵的是父View的大小如果改變,還需要再次重新設置Frame添怔⊥宕粒看著Frame這黃臉婆贤旷,心里一陣別扭...
我們來看看充滿青春活力的小鮮肉Masonry是怎么樣的:

UIEdgeInsets edge = UIEdgeInsetsMake(10, 10, 10, 10);
[view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(view.superview).insets(edge);
}];

使用mas_makeConstraints在block中對View添加約束。view相對父View的邊距為edge砾脑。
代碼簡單幼驶,邏輯一目了然。而且還能跟父View一起調整韧衣。簡直是perfect盅藻,初見Masonry,驚為天人

細品

cloudkite給Autolayout披上一層漂亮的外衣之后畅铭,將其稱為Masonry氏淑,但Masonry的本質還是Autolayout。

Autolayout是什么呢硕噩?Autolayout就是給View添加一堆約束假残,讓View在布局的時候通過約束計算出Frame,然后進行布局(Autolayout更多內(nèi)容見Autolayout的第一次親密接觸)炉擅。make.edges.equalTo(view.superview).insets(edge);就是添加約束的過程辉懒。

對于一個約束。他實際表示的是一個不等或者相等關系



用Masonry創(chuàng)建一個完整的約束應該是這樣的

//view1的左邊距離父View左邊10個點:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.equalTo(view1.superview.mas_left).multipliedBy(1).offset(10);
}];

對應到上圖的表達式:
Item1: make MASConstraintMaker類型谍失,view1的承載對象眶俩,表示View1
Attribute: left 表示左邊。left的make的屬性袱贮。返回值為MASConstraint類型
Relationship: equalTo 表示"="仿便。equalTo是MASConstraint的屬性
Item2: view1.superview
Attribute2: mas_left 同樣表示左邊,mas_left是Masonry給view加的屬性攒巍,為了不重名嗽仪,加了mas前綴
Multiplier: multipliedBy(1) 系數(shù)為1
Constant: offset(10) 常數(shù)為10

Attribute

MASConstraintMaker

上面的表達式中,我們可以看到柒莉,make是MASConstraintMaker類型闻坚。MASConstraintMaker給我們提供了22種Attribute類型

//Basic Attribute
@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;

//Margin Attribute
@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;

//Convenient Attribute
@property (nonatomic, strong, readonly) MASConstraint *edges;
@property (nonatomic, strong, readonly) MASConstraint *size;
@property (nonatomic, strong, readonly) MASConstraint *center;

Attribute總體來說分為三大類

  1. Basic Attribute: 基本屬性,支持到iOS6兢孝,一般使用得比較多
  2. Margin Attribute: 邊緣相關屬性窿凤,支持到iOS8。由于版本要求比較高跨蟹,一般用得比較少雳殊。Margin相關的詳細內(nèi)容請參考iOS8上關于UIView的Margin新增了3個APIs
  3. Convenient Attribute: 便捷屬性,為了使用方便而特意新增的屬性窗轩。Autolayout本身沒有對應的相關屬性

Convenient Attribute實際是基本屬性的組合夯秃。比如:edges表示left, right, top, bottom。
下面的兩個代碼實際的意義是一樣的

//Convenient Attribute
make.edges.insets(edge);

//Basic Attribute
make.left.right.top.bottom.insets(edge);

MASConstraint

前面我們看到MASConstraintMaker中所有的Attribute都是MASConstraint類型。對于多個Attribute一起寫的表達式:

make.left.right.top.bottom.insets(edge);

make.left返回的已經(jīng)是MASConstraint類型仓洼,也就是說right這個Attribute是MASConstraint的屬性介陶。
MASConstraint給我們提供了19種Attribute:

//Basic Attribute
@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;

//Margin Attribute
@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;

細看一下,MASConstraint中的Attribute和MASConstraintMaker完全一樣色建。只是MASConstraintMaker中多了3種Convenient Attribute哺呜。

兩者Attribute的一致,大大的提升了使用的方便性箕戳。使用過程中我們不用再去區(qū)分當前屬性是MASConstraint還是MASConstraintMaker類型某残。(事實上沒研究他的類型之前,我都不知道他們分別屬于2種不同類的屬性)

UIView(12月7日新增)

我們可以看到在.equalTo(view1.superview.mas_left)里面陵吸,superView也有Attribute驾锰。我們來看看UIView中有哪些Attribute:

// Basic Attribute
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_right;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leading;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_width;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_height;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;

// Margin Attribute
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerYWithinMargins;

可以看出,在UIView中的Attribute和MASConstraint中的幾乎一模一樣走越,只是每一個Attribute加了一個mas_前綴。

由于UIView是系統(tǒng)的類耻瑟,對其擴展屬性和方法一般都需要添加自己的前綴旨指,避免跟原有屬性和方法沖突。不過他們的意義跟MASConstraint中的Attribute是相同的

Relationship

約束表示的是2個item之間的關系喳整,在Autolayout中一共定義了3種關系:=, >=, <=谆构,對應到Masonry中:

- (MASConstraint * (^)(id attr))equalTo;
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;

相等關系我們一般用的多。那么不相等關系我們什么時候用呢框都?

假如我有一個Label搬素。Label的長度不能超出父View,如果label中的文字比較短魏保,我希望是文字有多長熬尺,Label就有多長。
由于label具有IntrinsicContentSize屬性谓罗。所以默認情況下粱哼,他是文字有多長,Label就有多長檩咱。(更多IntrinsicContentSize的內(nèi)容參見Autolayout的第一次親密接觸)揭措。所以我們只需要設置Label的長度小于父View即可

[label mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.offset(0);
    make.centerY.offset(0);
    make.width.lessThanOrEqualTo(label.superview);
}];

multiplier

multiplier表示Attribute前面的乘數(shù)。Masonry提供了2種添加multiplier的方法

//  Sets the NSLayoutConstraint multiplier property
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;

//  Sets the NSLayoutConstraint multiplier to 1.0/dividedBy
- (MASConstraint * (^)(CGFloat divider))dividedBy;

multipliedBy: 直接設置乘數(shù)
dividedBy: 設置乘數(shù)的倒數(shù) multiplier = 1.0/dividedBy
一般寬或者高的約束使用multiplier比較多

Constant

Masonry提供了4種設置constant的方法

//Modifies the NSLayoutConstraint constant,only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
- (MASConstraint * (^)(MASEdgeInsets insets))insets;

//Modifies the NSLayoutConstraint constant,only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following NSLayoutAttributeWidth, NSLayoutAttributeHeight
- (MASConstraint * (^)(CGSize offset))sizeOffset;

//Modifies the NSLayoutConstraint constant, only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
- (MASConstraint * (^)(CGPoint offset))centerOffset;

//Modifies the NSLayoutConstraint constant
- (MASConstraint * (^)(CGFloat offset))offset;

insets: 用來設置left, right, top, bottom刻蚯。接受MASEdgeInsets類型值
sizeOffset: 用來設置width, height绊含。接受CGSize類型的值
centerOffset: 用來設置centerX, centerY。接受CGPoint類型的值
offset: 可以用來設置所有的東西炊汹。接受CGFloat類型的值

其實一般情況下躬充,我只使用offset....

小技巧

  1. 如果等式2邊的Attribute是一樣的,我們可以省略等式右邊的Attribute
  2. 如果是等于關系,并且右邊的view是父View麻裳。連equalTo也可以省略
  3. 如果equalTo里面?zhèn)鞯氖荖SValue類型口蝠,效果跟設置offset是一樣的
  4. 如果offset為0,其實也是可以省略的...

下面所有代碼實際效果是一樣的:

// 完整的
make.left.equalTo(view1.superview.mas_left).offset(0);

//省略Attribute的
make.left.equalTo(view1.superview).offset(0);

//省略equalTo的
make.left.offset(0);

//使用equalTo替代offset的
make.left.equalTo(@0);

//終極大招津坑,省略所有的... 可惜會有warning
make.left;

不過對于make.left妙蔗,編譯器會報一個警告:你用getter方法獲取回來的值未使用,所以不應該使用"."語法


對于這個警告我們可以將返回值轉為空消除:

(void)make.left;

不過終究又變得麻煩了疆瑰,又要多寫6個字母眉反,愁人...

設置或更新約束

對于約束的設置,Masonry提供了3種方法穆役,分別為設置約束寸五、更新約束、重寫設置約束

// 設置約束 
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;

// 更新約束
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;

// 重新設置約束
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;

mas_makeConstraints: 初次設置約束使用耿币。
mas_updateConstraints: 更新約束時使用梳杏。如果找不著這條約束,會新增淹接,相當于mas_makeConstraints十性。
mas_remakeConstraints: 重新設置約束。先將view上所有約束移除塑悼,再新增約束

注意:mas_updateConstraints只能更新已有約束劲适。如果第一次使用的是left, right設置的相對寬度。更新的時候想換成使用width厢蒜。不能使用mas_updateConstraints霞势,因為已有約束里面沒有width的約束,新增width之后會跟原有l(wèi)eft, right約束沖突斑鸦。此時應該使用mas_remakeConstraints

批量設置約束

假設有View1愕贡,view2,view3三個View巷屿,我們想要他們的寬高都等于CGSizeMake(100, 50)颂鸿。我們可以對他們進行批量設置:

NSValue *sizeValue = [NSValue valueWithCGSize:CGSizeMake(100, 50)];
[@[view1,view2,view3] mas_makeConstraints:^(MASConstraintMaker *make) {
    make.size.equalTo(sizeValue);
}];

由于我們還要設置view的top,left等位置約束。那可不可以在設置位置的mas_makeConstraints里面批量設置寬高呢攒庵?實際是可以的嘴纺!

//advance set
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    (void)make.top.left;
    make.size.equalTo(@[view2,view3,sizeValue]);
}];

不過需要注意的是。設置約束的時候浓冒,view一定是已經(jīng)被addSubview的(詳情參考Autolayout的第一次親密接觸)栽渴,否則會拋異常。所以我們一般在最后一個view上加批量約束

Priority

我們知道約束是有優(yōu)先級的稳懒,Masonry給我們提供了4個設置優(yōu)先級的接口:

 // Sets the NSLayoutConstraint priority to a float or MASLayoutPriority
- (MASConstraint * (^)(MASLayoutPriority priority))priority;

//  Sets the NSLayoutConstraint priority to MASLayoutPriorityLow
- (MASConstraint * (^)())priorityLow;

//  Sets the NSLayoutConstraint priority to MASLayoutPriorityMedium
- (MASConstraint * (^)())priorityMedium;

//  Sets the NSLayoutConstraint priority to MASLayoutPriorityHigh
- (MASConstraint * (^)())priorityHigh;

priority: 可以設置任意的優(yōu)先級闲擦,接受的參數(shù)是0-1000的數(shù)字
priorityLow: 設置低優(yōu)先級慢味,優(yōu)先級為250
priorityMedium: 設置中優(yōu)先級,優(yōu)先級為500
priorityHigh: 設置高優(yōu)先級墅冷,優(yōu)先級為750

需要注意的是纯路,使用priorityLow、priorityMedium寞忿、priorityHigh的時候驰唬。不是.priorityHigh,而是.priorityHigh()

key

當約束沖突發(fā)生的時候腔彰,我們經(jīng)常為找不到是哪個View沖突的而煩惱叫编,這一堆View是個什么東西呀?

"<MASLayoutConstraint:0x7f8de483fb10 UIView:0x7f8de2f53870.left == UIView:0x7f8de2f586c0.left>",
"<MASLayoutConstraint:0x7f8de4818b50 UIView:0x7f8de2f53870.right == UIView:0x7f8de2f586c0.right>",
"<MASLayoutConstraint:0x7f8de4818870 UIView:0x7f8de2f53870.width == 100>",
"<NSLayoutConstraint:0x7f8de4847e90 UIView:0x7f8de2f586c0.width == 375>"

Will attempt to recover by breaking constraint 
<MASLayoutConstraint:0x7f8de4818870 UIView:0x7f8de2f53870.width == 100>

這時候我們可以設置View的key:

self.view.mas_key = @"self.view";
view1.mas_key = @"view1";

設置之后再看一下霹抛,哈哈搓逾,現(xiàn)在好多了”眨可以清晰的知道是哪個view了

"<MASLayoutConstraint:0x7fcd98d17c40 UIView:view1.left == UIView:self.view.left>",
"<MASLayoutConstraint:0x7fcd98d2b2c0 UIView:view1.right == UIView:self.view.right>",
"<MASLayoutConstraint:0x7fcd98d2adb0 UIView:view1.width == 100>",
"<NSLayoutConstraint:0x7fcd98e42050 UIView:self.view.width == 375>"

Will attempt to recover by breaking constraint 
<MASLayoutConstraint:0x7fcd98d2adb0 UIView:view1.width == 100>

大家可能會覺得這樣一個一個設置霞篡,多麻煩啊端逼!別著急寇损,Masonry提供了批量設置的宏MASAttachKeys
只需要一句代碼即可全部設置:

MASAttachKeys(self.view,view1);

Shorthand(12月7日新增)

在寫代碼的時候,可能你會感覺有的東西要加mas_前綴裳食,有的東西又不用加,代碼風格不統(tǒng)一芙沥,而且加mas_前綴還麻煩诲祸。

前面介紹過加mas_前綴主要是在擴展系統(tǒng)類的時候為了避免與原有類沖突,這是Apple推薦的做法而昨。不過目前來說救氯,即使不加mas_前綴,也不會有什么問題歌憨。所以Masonry提供了不加mas_前綴的方法着憨,只需要你定義幾個宏即可。

  1. MAS_SHORTHAND
    定義MAS_SHORTHAND宏之后务嫡〖锥叮可以使用UIView,NSArray中不帶mas_前綴的makeConstraints,updateConstraints,remakeConstraints心铃。以及UIView中不帶mas_前綴的Attribute准谚。

  2. MAS_SHORTHAND_GLOBALS
    默認的equalTo方法只接受id類型的對象。有時候我們想傳入一個CGFloat, CGSize, UIEdgeInsets等去扣。還需要將其轉化成NSValue對象柱衔,比較麻煩。Masonry也考慮到了這種情況。只需要定義MAS_SHORTHAND_GLOBALS宏唆铐。就可以直接對equalTo傳入基礎類型哲戚。Masonry自動轉化成NSValue對象

撥開Masonry的衣服

Masonry的基本使用方法介紹完了,那么我們來看看Masonry的內(nèi)部到底有些什么東西艾岂?

結構

Masonry一共有十三個類顺少,我將這13個類分為5個模塊:


Help

Help模塊主要是一些輔助的類。

NSLayoutConstraint+MASDebugAdditions:這個類的主要作用是重寫NSLayoutConstraint的description函數(shù)澳盐。讓約束發(fā)生沖突的時候祈纯,更易讀。如果View或者constraint設置了Key叼耙,直接用key的值顯示到description中腕窥。如果沒有設置,顯示View或者constraint的指針筛婉。

ViewController+MASAdditions:提供了ViewController的LayoutGuide相關屬性簇爆,以便View對齊時使用
MASUtilities:定義了一些公用的宏和屬性

Shorthand

對于系統(tǒng)原有類(NSArray,UIView)的擴展。Masonry的category方法和屬性都加有mas_前綴爽撒。這也是Apple建議的做法入蛆,避免跟系統(tǒng)原有方法沖突。但是有時候我們可能想用的更方便硕勿,不想寫mas_前綴(沒辦法哨毁,我就是這么懶...)

NSArray+MASShorthandAdditionsView+MASShorthandAdditions中定義了不帶mas_前綴的擴展。這些擴展根據(jù)你是否定義了MAS_SHORTHAND宏來確定是否編譯源武。所以你只需要定義MAS_SHORTHAND宏扼褪,就可以方便的使用不帶mas_前綴的方法,比如:-[view makeConstraints:]

Public

Public模塊主要是對外暴露的方法粱栖。使用者使用Masonry可以直接接觸到话浇。

NSArray+MASAdditions:主要有定義和更新約束的方法,如mas_makeConstraints:
View+MASAdditions:除了定義和更新約束的一系列方法之外闹究,還為View增加了mas_top, mas_left等Attribute屬性

Core

Core模塊就是Masonry的核心部分幔崖,Masonry的大部分功能都在這4個類里實現(xiàn)

MASConstraintMaker:約束控制器≡伲控制更新赏寇,刪除,或者新增約束
MASConstraint:約束的基類价认,虛類蹋订。定義了Constraint的基本屬性和方法。
MASViewConstraint: 約束的主要實現(xiàn)類刻伊。所有對約束使用的功能均在此類中完成
MASCompositeConstraint:約束的集合類露戒。內(nèi)部有一個數(shù)組椒功,可以保存多個MASViewConstraint。對MASCompositeConstraint調用方法實際等于對其內(nèi)部的所有MASViewConstraint調用方法

Property

此模塊主要封裝了一些MASConstraint持有的屬性智什。為了使用更方便动漾,或者擴展功能

MASViewAttribute:每一個Attribute都有一個View與之對應,為了使用更方便荠锭,所以將他們通過一個類封裝在一起
MASLayoutConstraint:默認的NSLayoutConstraint是沒有Key這個屬性的旱眯,為了Debug方便。派生一個子類证九,持有key屬性

實現(xiàn)

當我們給View添加一個約束的時候到底發(fā)生了什么删豺?

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.top.equalTo(view1.superview).offset(20);
}];

我們首先來看make.left.top.equalTo(view1.superview).offset(20);

一、執(zhí)行"make.left"

MASConstraintMaker類中有一個屬性constraints專門用來存儲constraint

@property (nonatomic, strong) NSMutableArray *constraints;

當執(zhí)行make.left的時候, 會將相應的MASConstraint添加到constraints數(shù)組中

- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}   

//核心方法
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    
    //調用make.left.top時走入這里將原來的ViewConstraint替換成MASCompositeConstraint
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    
    // 調用make.left的時候走入這里愧怜,將constraint加入到self.constraints中
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

對MASConstraintMaker調用Attribute的get方法呀页,最終都會走到-constraint:addConstraintWithLayoutAttribute:中,在這個方法中拥坛,通過對應的Attribute生成MASViewConstraint蓬蝶。然后將MASViewConstraint加入到constraints中

二、執(zhí)行".top"

make.left返回的是MASConstraint類型猜惋。所以make.left.top是對MASViewConstraint類型調用top方法丸氛。

- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

當執(zhí)行-addConstraintWithLayoutAttribute的時候,ViewConstraint通過delegate又調回到MASConstraintMaker的-constraint:addConstraintWithLayoutAttribute:中著摔。

在MASConstraintMaker的-constraint:addConstraintWithLayoutAttribute:里缓窜,將原來constraints中的MASViewConstraint替換成MASCompositeConstraint。MASCompositeConstraint持有top,left 2個屬性谍咆。對MASCompositeConstraint做操作時候禾锤,其內(nèi)部的所有屬性都會執(zhí)行相應的操作

三、執(zhí)行".equalTo(view1.superview)"

- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.secondViewAttribute = attr;
                [children addObject:viewConstraint];
            }
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else {
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

當執(zhí)行Relationship的方法時卧波,都會走到-equalToWithRelation中。
在這個方法里面主要是給realationship和secondViewAttribute賦值:

  1. 如果不是數(shù)組庇茫,直接對realationship和secondViewAttribute賦值
  2. 如果是數(shù)組港粱,如:.equalTo(@[view1.mas_left,view2.mas_left]),邏輯上肯定不能是不等關系(>=,<=)旦签,所以realationship不用賦值查坪,使用默認值(=)。copy出多個viewConstraint宁炫,將secondViewAttribute賦值偿曙。然后用多個viewConstraint組成的compositeConstraint替換調原來的viewConstraint。

四羔巢、執(zhí)行".offset(10)"

- (MASConstraint * (^)(CGFloat))offset {
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}

- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}

offset(10)會將10傳入到ViewConstraint中望忆,用layoutConstant屬性將其存起來罩阵。(offset主要影響的是約束里面的constant)

五、mas_makeConstraints

看完了make.left.top.equalTo(view1.superview).offset(20);启摄,我們再看看mas_makeConstraints中到底做了什么稿壁?

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

mas_makeConstraints方法很簡單,

  1. 將self.translatesAutoresizingMaskIntoConstraints至為NO歉备。translatesAutoresizingMaskIntoConstraints表示是否將設置的Frame轉化為約束傅是。當自己設置約束的時候需要將其置為NO
  2. 創(chuàng)建出MASConstraintMaker對象
  3. 通過block拋出到外面設值
  4. constraintMaker install

上面的代碼我們知道,關鍵的地方還是在于constraintMaker install

六蕾羊、constraintMaker install

- (NSArray *)install {
    if (self.removeExisting) {
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }
    NSArray *constraints = self.constraints.copy;
    for (MASConstraint *constraint in constraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}
  1. 如果需要removeExisting喧笔,就把已有的約束remove掉,當調用mas_remakeConstraints的時候會將removeExisting值置為YES
  2. 遍歷constraints龟再,調用[constraint install]
  3. 清空constraints书闸,這里的constraintMaker只是一個零時屬性,只是一個工具類吸申,不需要存儲梗劫。所以用完之后就可以將constraints清空

其實真正關鍵的地方在[constraint install]

七、constraint install

- (void)install {
    // 1. 已經(jīng)installed的將不做任何操作
    if (self.hasBeenInstalled) {
        return;
    }
    
    //2. 從ViewAttribute中剝離出item和attribute
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

    //3. 如果沒有secondViewAttribute截碴,默認secondItem為其父View梳侨,secontAttribute等于firstLayoutAttribute。
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    
    //4. 創(chuàng)建真正用于Autolayout的約束layoutConstraint
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
    //5. 將priority和key賦值
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;
    
    //6. 找到要添加約束的installView
    if (self.secondViewAttribute.view) {
        MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
        NSAssert(closestCommonSuperview,
                 @"couldn't find a common superview for %@ and %@",
                 self.firstViewAttribute.view, self.secondViewAttribute.view);
        self.installedView = closestCommonSuperview;
    } else if (self.firstViewAttribute.isSizeAttribute) {
        self.installedView = self.firstViewAttribute.view;
    } else {
        self.installedView = self.firstViewAttribute.view.superview;
    }

    //7. 添加或更新約束
    MASLayoutConstraint *existingConstraint = nil;
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) {
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}
  1. 如果已經(jīng)installed就不做任何操作
  2. 從ViewAttribute中剝離出item和attribute日丹。前面我們介紹過MASViewAttribute的類主要是將item和attribute2個屬性封裝在了一起走哺。
  3. secondViewAttribute的值來自于.equalTo(item.attribute)中的item.attribute。當我們寫下類似make.left.offset(10);約束的時候哲虾,是沒有secondViewAttribute的丙躏,這時候默認secondItem為其父View,secontAttribute等于firstLayoutAttribute束凑。這就解釋了為什么可以這樣寫make.left.offset(10);
  4. 創(chuàng)建真正用于Autolayout的約束layoutConstraint
  5. 將priority和key賦值
  6. 找到要添加約束的installView晒旅。如果是2個View之間的約束,需要尋找這2個View最接近的共同父View汪诉。添加約束
  7. 添加或更新約束废恋。當調用mas_updateConstraints的時候updateExisting=YES。這時候會查找是否有已經(jīng)存在的約束扒寄。有就更新鱼鼓,沒有就添加。如果是mas_makeConstraints或mas_remakeConstraints该编,則直接添加

Extension

僅僅將代碼結構和基本實現(xiàn)過程解析了一下迄本,更多實現(xiàn)細節(jié)還需要大家自己去閱讀源碼

說實話,Masonry的代碼寫得真漂亮课竣,不管是代碼格式規(guī)范嘉赎,還是設計模式置媳。看起來簡直是一種享受曹阔。建議大家閱讀半开。

Autolayout的第一次親密接觸也更新了一些東西。沒閱讀過或者閱讀時間比較早的朋友可以再看看~

Reference

Masonry源碼
Autolayout的第一次親密接觸
iOS8上關于UIView的Margin新增了3個APIs

About me

我的博客
我的微博

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赃份,一起剝皮案震驚了整個濱河市寂拆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抓韩,老刑警劉巖纠永,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谒拴,居然都是意外死亡尝江,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門英上,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炭序,“玉大人,你說我怎么就攤上這事苍日〔涯簦” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵相恃,是天一觀的道長辜纲。 經(jīng)常有香客問我,道長拦耐,這世上最難降的妖魔是什么耕腾? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮杀糯,結果婚禮上扫俺,老公的妹妹穿的比我還像新娘。我一直安慰自己固翰,他們只是感情好狼纬,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著倦挂,像睡著了一般畸颅。 火紅的嫁衣襯著肌膚如雪担巩。 梳的紋絲不亂的頭發(fā)上方援,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音涛癌,去河邊找鬼犯戏。 笑死送火,一個胖子當著我的面吹牛费什,可吹牛的內(nèi)容都是我干的蝙搔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惊豺,長吁一口氣:“原來是場噩夢啊……” “哼呀非!你這毒婦竟也來了坚俗?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤岸裙,失蹤者是張志新(化名)和其女友劉穎猖败,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體降允,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡恩闻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了剧董。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幢尚。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翅楼,靈堂內(nèi)的尸體忽然破棺而出尉剩,到底是詐尸還是另有隱情,我是刑警寧澤犁嗅,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布边涕,位于F島的核電站,受9級特大地震影響褂微,放射性物質發(fā)生泄漏功蜓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一宠蚂、第九天 我趴在偏房一處隱蔽的房頂上張望式撼。 院中可真熱鬧,春花似錦求厕、人聲如沸著隆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽美浦。三九已至,卻和暖如春项栏,著一層夾襖步出監(jiān)牢的瞬間浦辨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工沼沈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留流酬,地道東北人币厕。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像芽腾,于是被迫代替她去往敵國和親旦装。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 本文內(nèi)容全部轉載自追求Masonry 目錄 『使用』 一摊滔、MASConstraintMaker二阴绢、MASConst...
    Vinc閱讀 3,545評論 4 18
  • iOS_autoLayout_Masonry 概述 Masonry是一個輕量級的布局框架與更好的包裝AutoLay...
    指尖的跳動閱讀 1,165評論 1 4
  • 轉載:https://www.cnblogs.com/liutingIOS/p/5406858.html 一、Ma...
    JasonYuan123閱讀 1,372評論 0 1
  • Masonry是一個輕量級的布局框架艰躺,擁有自己的描述語法旱函,采用更優(yōu)雅的鏈式語法封裝自動布局,簡潔明了并具有高可讀性...
    3dcc6cf93bb5閱讀 1,768評論 0 1
  • (一)Masonry介紹 Masonry是一個輕量級的布局框架 擁有自己的描述語法 采用更優(yōu)雅的鏈式語法封裝自動布...
    木易林1閱讀 2,338評論 0 3