Masonry

Autolayout就像一個知情達理,善解人意的好姑娘,惋惜長相有點不堪入目,因此追求者寥寥無幾。所幸遇到了化妝大師cloudkite,給她來了一個完美的化妝,今後醜小鴨Autolayout變成了美天鵝Masonry。前幾日有幸一見,果真名不虛傳,長相甜美,還善解人意。我果斷放棄了Frame,開始追求Masonryphp

初識Masonry

初見

咱們先來看看Masonry到底有多美。
我要設置一個containView,他距離superView的上下左右邊距都是10。
若是我用frame,應該是這樣寫的:css

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是怎麼樣的:html

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,驚爲天人git

細品

cloudkite給Autolayout披上一層漂亮的外衣以後,將其稱爲Masonry,但Masonry的本質仍是Autolayout。github

Autolayout是什麼呢?Autolayout就是給View添加一堆約束,讓View在佈局的時候經過約束計算出Frame,而後進行佈局(Autolayout更多內容見Autolayout的第一次親密接觸)。make.edges.equalTo(view.superview).insets(edge);就是添加約束的過程。sql

對於一個約束。他實際表示的是一個不等或者相等關係swift


 


用Masonry建立一個完整的約束應該是這樣的設計模式

//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) 係數爲1
Constant: offset(10) 常數爲10api

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相關的詳細內容請參考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返回的已是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的一致,大大的提高了使用的方便性。使用過程當中咱們不用再去區分當前屬性是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是系統的類,對其擴展屬性和方法通常都須要添加本身的前綴,避免跟原有屬性和方法衝突。不過他們的意義跟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的內容參見Autolayout的第一次親密接觸)。因此咱們只須要設置Label的長度小於父View便可

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

multiplier

multiplier表示Attribute前面的乘數。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: 直接設置乘數
dividedBy: 設置乘數的倒數 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裏面傳的是NSValue類型,效果跟設置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以後會跟原有left, 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必定是已經被addSubview的(詳情參考Autolayout的第一次親密接觸),不然會拋異常。因此咱們通常在最後一個view上加批量約束

Priority

咱們知道約束是有優先級的,Masonry給咱們提供了4個設置優先級的接口:

//    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: 能夠設置任意的優先級,接受的參數是0-1000的數字
priorityLow: 設置低優先級,優先級爲250
priorityMedium: 設置中優先級,優先級爲500
priorityHigh: 設置高優先級,優先級爲750

須要注意的是,使用priorityLow、priorityMedium、priorityHigh的時候。不是.priorityHigh,而是.priorityHigh()

key

當約束衝突發生的時候,咱們常常爲找不到是哪一個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";

設置以後再看一下,哈哈,如今好多了。能夠清晰的知道是哪一個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_前綴,有的東西又不用加,代碼風格不統一,並且加mas_前綴還麻煩。

前面介紹過加mas_前綴主要是在擴展系統類的時候爲了不與原有類衝突,這是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的內部到底有些什麼東西?

結構

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


 

Help

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

NSLayoutConstraint+MASDebugAdditions:這個類的主要做用是重寫NSLayoutConstraint的description函數。讓約束髮生衝突的時候,更易讀。若是View或者constraint設置了Key,直接用key的值顯示到description中。若是沒有設置,顯示View或者constraint的指針。

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

Shorthand

對於系統原有類(NSArray,UIView)的擴展。Masonry的category方法和屬性都加有mas_前綴。這也是Apple建議的作法,避免跟系統原有方法衝突。可是有時候咱們可能想用的更方便,不想寫mas_前綴(沒辦法,我就是這麼懶...)

NSArray+MASShorthandAdditionsView+MASShorthandAdditions中定義了不帶mas_前綴的擴展。這些擴展根據你是否認義了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個類裏實現

MASConstraintMaker:約束控制器。控制更新,刪除,或者新增約束
MASConstraint:約束的基類,虛類。定義了Constraint的基本屬性和方法。
MASViewConstraint: 約束的主要實現類。全部對約束使用的功能均在此類中完成
MASCompositeConstraint:約束的集合類。內部有一個數組,能夠保存多個MASViewConstraint。對MASCompositeConstraint調用方法實際等於對其內部的全部MASViewConstraint調用方法

Property

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

MASViewAttribute:每個Attribute都有一個View與之對應,爲了使用更方便,因此將他們經過一個類封裝在一塊兒
MASLayoutConstraint:默認的NSLayoutConstraint是沒有Key這個屬性的,爲了Debug方便。派生一個子類,持有key屬性

實現

當咱們給View添加一個約束的時候到底發生了什麼?

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

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

1、執行"make.left"

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

@property (nonatomic, strong) NSMutableArray *constraints;

當執行make.left的時候, 會將相應的MASConstraint添加到constraints數組中

- (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中

2、執行".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]; }

當執行-addConstraintWithLayoutAttribute的時候,ViewConstraint經過delegate又調回到MASConstraintMaker的-constraint:addConstraintWithLayoutAttribute:中。

在MASConstraintMaker的-constraint:addConstraintWithLayoutAttribute:裏,將原來constraints中的MASViewConstraint替換成MASCompositeConstraint。MASCompositeConstraint持有top,left 2個屬性。對MASCompositeConstraint作操做時候,其內部的全部屬性都會執行相應的操做

3、執行".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; } }; }

當執行Relationship的方法時,都會走到-equalToWithRelation中。
在這個方法裏面主要是給realationship和secondViewAttribute賦值:

  1. 若是不是數組,直接對realationship和secondViewAttribute賦值
  2. 若是是數組,如:.equalTo(@[view1.mas_left,view2.mas_left]),邏輯上確定不能是不等關係(>=,<=),因此realationship不用賦值,使用默認值(=)。copy出多個viewConstraint,將secondViewAttribute賦值。而後用多個viewConstraint組成的compositeConstraint替換調原來的viewConstraint。

4、執行".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)

5、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. 建立出MASConstraintMaker對象
  3. 經過block拋出到外面設值
  4. constraintMaker install

上面的代碼咱們知道,關鍵的地方仍是在於constraintMaker install

6、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]

7、constraint install

- (void)install { // 1. 已經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. 建立真正用於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. 若是已經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. 建立真正用於Autolayout的約束layoutConstraint
  5. 將priority和key賦值
  6. 找到要添加約束的installView。若是是2個View之間的約束,須要尋找這2個View最接近的共同父View。添加約束
  7. 添加或更新約束。當調用mas_updateConstraints的時候updateExisting=YES。這時候會查找是否有已經存在的約束。有就更新,沒有就添加。若是是mas_makeConstraints或mas_remakeConstraints,則直接添加

Extension

僅僅將代碼結構和基本實現過程解析了一下,更多實現細節還須要你們本身去閱讀源碼

說實話,Masonry的代碼寫得真漂亮,無論是代碼格式規範,仍是設計模式。看起來簡直是一種享受。建議你們閱讀。

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

Reference

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

原文做者的微、博:

個人博客
個人微博

--------------------簡單使用-------------------

基本的計算公式爲

控件左邊 = 參考控件的右邊 + 偏移值(5) (控件在參考控件的右邊,距離其5px)

make.left.equalTo(view.superview.mas_right).offset(10);//不填則默認對應left,其餘同理

 

Masonry有三種設置約束的方法

mas_makeConstraints //第一次生成約束使用 mas_updateConstraints //更新其中的約束 mas_remakeConstraints //從新生成約束,會將以前的全部約束先去掉

使用注意:在循環cell,若是有代碼重複調用的地方,必定要使用mas_remakeConstraints,以此防止循環的時候生成相同的約束,影響性能,甚至,能使用make的地方基本均可以用remake進行代替,防止生成無謂的約束

簡單用法

初始化一個帶邊距的view

UIView *view = [[UIView alloc] init]; view.backgroundColor = [UIColor redColor]; [self.view addSubview:view];//必定要先加入父控件,不然報錯 [view mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(view.superview).insets(UIEdgeInsetsMake(20, 20, 20, 20)); }];

等價

[view mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.bottom.equalTo(view.superview).insets(UIEdgeInsetsMake(20, 20, 20, 20));

left ,right等屬性,如字面意思

等價

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

鏈式語法中,and 以及 with都是修飾性語句,不作任何事情,便於理解而已

make.bottom.and.top.equalTo(view.superview).with.offset(-20);

源碼中

#pragma mark - Semantic properties - (MASConstraint *)with { return self; } - (MASConstraint *)and { return self; }

 

子控件寬高爲父控件的一半(multipliedBy)

[view mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(view.superview); make.top.equalTo(view.superview).offset(20); make.width.height.equalTo(view.superview).multipliedBy(0.5); }];

 

大於小於

make.width.greaterThanOrEqualTo(@200); make.width.lessThanOrEqualTo(@400)

blcok中進行判斷使用約束(在統一處理某些業務的時候)

[self.button mas_remakeConstraints:^(MASConstraintMaker *make) { make.size.equalTo(self.buttonSize); if (topLeft) { make.top.and.left.offset(10); } else { make.bottom.and.right.offset(-10); } }];死高度300 * 300

修改指定約束

MASConstraint *topConstraint; // 在生成約束的時候 [view1 mas_makeConstraints:^(MASConstraintMaker *make) { topConstraint = make.top.equalTo(superview.mas_top); make.left.equalTo(superview.mas_left); }]; ... // 在以後進行對該約束 進行修改 [topConstraint uninstall];

寫死高度300 * 300

[view mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(view.superview); make.width.height.equalTo(@300); }];

關於mas_equalTo使用

Masonry表示相等有兩種方法,equalTo 和 mas_equalTo

mas_equalTo實際上是多了一層處理的宏而已,由於equalTo並不支持基本數據類型

#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))

在高度爲300的約束中,能夠這樣子寫

mak.height.equalTo(@300);

也能夠,使用mas_equalTo,通常狀況下,我會所有使用mas_equalTo來處理基本數據類型的封裝

mak.height.mas_equalTo(300);

 

並列排序-水平 或者 高度

常常會遇到不少須要等寬或者登高排序的需求,下面是我我的使用的一種方法,能夠參考一下,可是須要說明的是,相對佈局的各類用法不少,請思考即可以,同一種效果,N種寫法

- (void)viewDidLoad { [super viewDidLoad]; //都是相對於suerpview來設置位置的 NSMutableArray *viewArray = [NSMutableArray array]; NSArray *colorArray = @[[UIColor redColor],[UIColor blueColor],[UIColor orangeColor],[UIColor purpleColor]]; for (int i = 0; i < colorArray.count ; i++) { UIView *view = [[UIView alloc] init]; view.backgroundColor = colorArray[i]; [self.view addSubview:view]; [viewArray addObject:view]; } [self sortVerticalWithViews:viewArray LeftMargin:50 Width:100 BackViewHeight:300]; viewArray = [NSMutableArray array]; for (int i = 0; i < colorArray.count ; i++) { UIView *view = [[UIView alloc] init]; view.backgroundColor = colorArray[i]; [self.view addSubview:view]; [viewArray addObject:view]; } [self sortHorizontalWithViews:viewArray TopMargin:320 TopView:self.view Height:100]; } #pragma mark 將控件進行排序,更新其操做(水平) - (void)sortHorizontalWithViews:(NSArray *)views TopMargin:(NSInteger)topMargin TopView:(UIView *)topView Height:(NSInteger)viewH { __block UIView *leftView; [views enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { UIView *tempView = obj; [tempView mas_remakeConstraints:^(MASConstraintMaker *make) { if (idx == 0) { make.left.equalTo(tempView.superview); if ([topView isEqual:tempView.superview]) { //若是傳入的是容器view 則上方無控件 make.top.mas_equalTo(topView).offset(topMargin); } else { make.top.mas_equalTo(topView.mas_bottom).offset(topMargin); } make.width.mas_equalTo(tempView.superview.mas_width).multipliedBy((CGFloat)1 / views.count); make.height.mas_equalTo(viewH); } else { make.left.equalTo(leftView.mas_right); make.top.mas_equalTo(leftView); make.width.mas_equalTo(leftView); make.height.equalTo(leftView); } }]; leftView = tempView; }]; } #pragma mark 將控件進行排序,更新其操做(垂直) - (void)sortVerticalWithViews:(NSArray *)views LeftMargin:(NSInteger)leftMargin Width:(NSInteger)viewWidth BackViewHeight:(NSInteger)backViewHeight { __block UIView *topView; [views enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { UIView *tempView = obj; [tempView mas_remakeConstraints:^(MASConstraintMaker *make) { if (idx == 0) { make.left.equalTo(tempView.superview).offset(leftMargin); make.top.mas_equalTo(tempView.superview); make.width.mas_equalTo(viewWidth); make.height.mas_equalTo(backViewHeight / views.count); } else { make.left.equalTo(topView); make.top.mas_equalTo(topView.mas_bottom); make.width.mas_equalTo(topView); make.height.mas_equalTo(topView); } }]; topView = tempView; }]; }

這裏寫圖片描述

動畫問題

動畫問題,和普通的方法實現差很少,重點只是修改約束後調用

[view.superview layoutIfNeeded];

而已

[view mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(400); make.left.mas_equalTo(100); make.size.mas_equalTo(CGSizeMake(100, 100)); }]; [view.superview layoutIfNeeded];//若是其約束尚未生成的時候須要動畫的話,就請先強制刷新後才寫動畫,不然全部沒生成的約束會直接跑動畫 [UIView animateWithDuration:3 animations:^{ [view mas_updateConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(200); }]; [view.superview layoutIfNeeded];//強制繪製 }];

Cell的高度計算

借鑑@星光社的戴銘 的方法 [AutoLayout框架Masonry使用心得]
(http://www.jianshu.com/p/24e4ff56bfea)
也能夠參考forkingdog的FDTemplateLayoutCell

  • iOS8 以上
tableView.rowHeight = UITableViewAutomaticDimension; tableView.estimatedRowHeight = 80; //減小第一次計算量,iOS7後支持 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return UITableViewAutomaticDimension;//返回便可 }
  • iOS7
//在model中添加屬性緩存高度 @interface DataModel : NSObject @property (copy, nonatomic) NSString *text; @property (assign, nonatomic) CGFloat cellHeight; //緩存高度 @end - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { static CustomCell *cell; //只初始化一次cell static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([CustomCell class])]; }); DataModel *model = self.dataArray[(NSUInteger) indexPath.row]; if (model.cellHeight <= 0) { [cell makeupData:model]; //使用systemLayoutSizeFittingSize獲取高度 model.cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 1; } return model.cellHeight; }

遇到的問題

AutoLayout是在iOS7 以後纔剛剛推出的一種新的方法,由於在iOS7系統上並不能算得上十分完善,常常有一些bug,然而在iOS8中相對好的處理了

  • 最明顯的問題就是scorllView的contentSize問題

  • 系統的約束並非在設置完成以後裏面進行繪圖的,而是在最後ViewDidApper()這個函數前一些時間才完成繪圖,並且每次繪製(好比,往scorllView上添加新的子控件,即增長了新的約束)後,scorllView的contentOffset和contentSize都會初始化爲0,所以每次都須要從新設置(若是你在繪製前已經設置了contentSize的話),或者你可使用tableView來代替scorllView

- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; _scorllView.contentSize = CGSizeMake(200, 200); }
  • 若是你須要在約束設置完成後立馬獲得frame的數值的話,調用
[view.superview layoutIfNeeded];

以後會強制性更新約束,這句話以後即可以獲得frame,在iOS8中只須要在這加入設置contentSize即可以實現正常的scrollView滾動,而iOS7中則不能夠,請注意。

並且若是出現什麼疑難雜症的話,基本都是AutoLayout在iOS的不適用,因此搜索問題的話,各位直接搜索Autolayout 關鍵字即可,沒必要搜索Masonry關鍵字的問題(反正也搜不到什麼答案...)

  • contentView的衝突
  • 若是遇到和contentView的衝突,基本緣由是由於cell的content view有一個系統的約束(高度),而masonry是不會去管理非本身產生的約束,所以在使用label imageview等狀況下,增長如下屬性設置,確保優先級以防止衝突
[_contentLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

隨筆

之因此開始寫blog,只由於,梳理blog的過程當中,會讓本身懂得更多。

最後附上[不按期更新]

看到比較好的blog, 在此附上, 向各位大神學習(串哥好像還出了直播講解masonry,等視頻上傳了去觀摩下)

裏脊串的開發隨筆 - 《Masonry介紹與使用實踐(快速上手Autolayout)》

裏脊串的開發隨筆 - 《如何使用Masonry設計複合型cell》

星光社的戴銘 - 《AutoLayout框架Masonry使用心得》

小笨狼 -- 《追求Masonry》

相關連接:

追求Masonry

如何使用masonry設計複合型cell

iOS Autolayout之Masonry解讀

Masonry用法總結

Masonry總體動畫更新約束

Masonry的使用,動畫,出現問題解決等

iOS開發筆記--使用Auto Layout中的VFL(Visual format language)--代碼實現自動佈局

iOS Masonry的使用須要注意的地方

相關文章
相關標籤/搜索