Autolayout就像一個知情達理,善解人意的好姑娘,惋惜長相有點不堪入目,因此追求者寥寥無幾。所幸遇到了化妝大師cloudkite
,給她來了一個完美的化妝,今後醜小鴨Autolayout變成了美天鵝Masonry。前幾日有幸一見,果真名不虛傳,長相甜美,還善解人意。我果斷放棄了Frame,開始追求Masonryphp
咱們先來看看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的承載對象,表示View1Attribute
: left 表示左邊。left的make的屬性。返回值爲MASConstraint類型Relationship
: equalTo 表示"="。equalTo是MASConstraint的屬性Item2
: view1.superviewAttribute2
: mas_left 一樣表示左邊,mas_left是Masonry給view加的屬性,爲了避免重名,加了mas前綴Multiplier
: multipliedBy(1) 係數爲1Constant
: offset(10) 常數爲10api
上面的表達式中,咱們能夠看到,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整體來講分爲三大類
Convenient Attribute實際是基本屬性的組合。好比:edges表示left, right, top, bottom。
下面的兩個代碼實際的意義是同樣的
//Convenient Attribute make.edges.insets(edge); //Basic Attribute make.left.right.top.bottom.insets(edge);
前面咱們看到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種不一樣類的屬性)
咱們能夠看到在.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是相同的
約束表示的是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表示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比較多
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....
下面全部代碼實際效果是同樣的:
// 完整的 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上加批量約束
咱們知道約束是有優先級的,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
: 設置低優先級,優先級爲250priorityMedium
: 設置中優先級,優先級爲500priorityHigh
: 設置高優先級,優先級爲750
須要注意的是,使用priorityLow、priorityMedium、priorityHigh的時候。不是.priorityHigh
,而是.priorityHigh()
當約束衝突發生的時候,咱們常常爲找不到是哪一個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);
在寫代碼的時候,可能你會感受有的東西要加mas_前綴,有的東西又不用加,代碼風格不統一,並且加mas_前綴還麻煩。
前面介紹過加mas_前綴主要是在擴展系統類的時候爲了不與原有類衝突,這是Apple推薦的作法。不過目前來講,即便不加mas_前綴,也不會有什麼問題。因此Masonry提供了不加mas_前綴的方法,只須要你定義幾個宏便可。
MAS_SHORTHAND
定義MAS_SHORTHAND宏以後。可使用UIView,NSArray中不帶mas_前綴的makeConstraints,updateConstraints,remakeConstraints。以及UIView中不帶mas_前綴的Attribute。
MAS_SHORTHAND_GLOBALS
默認的equalTo方法只接受id類型的對象。有時候咱們想傳入一個CGFloat, CGSize, UIEdgeInsets等。還須要將其轉化成NSValue對象,比較麻煩。Masonry也考慮到了這種狀況。只須要定義MAS_SHORTHAND_GLOBALS宏。就能夠直接對equalTo傳入基礎類型。Masonry自動轉化成NSValue對象
Masonry的基本使用方法介紹完了,那麼咱們來看看Masonry的內部到底有些什麼東西?
Masonry一共有十三個類,我將這13個類分爲5個模塊:
Help模塊主要是一些輔助的類。
NSLayoutConstraint+MASDebugAdditions
:這個類的主要做用是重寫NSLayoutConstraint的description函數。讓約束髮生衝突的時候,更易讀。若是View或者constraint設置了Key,直接用key的值顯示到description中。若是沒有設置,顯示View或者constraint的指針。
ViewController+MASAdditions
:提供了ViewController的LayoutGuide相關屬性,以便View對齊時使用MASUtilities
:定義了一些公用的宏和屬性
對於系統原有類(NSArray,UIView)的擴展。Masonry的category方法和屬性都加有mas_前綴。這也是Apple建議的作法,避免跟系統原有方法衝突。可是有時候咱們可能想用的更方便,不想寫mas_前綴(沒辦法,我就是這麼懶...)
在NSArray+MASShorthandAdditions
和View+MASShorthandAdditions
中定義了不帶mas_前綴的擴展。這些擴展根據你是否認義了MAS_SHORTHAND宏來肯定是否編譯。因此你只須要定義MAS_SHORTHAND宏,就能夠方便的使用不帶mas_前綴的方法,好比:-[view makeConstraints:]
Public模塊主要是對外暴露的方法。使用者使用Masonry能夠直接接觸到。
NSArray+MASAdditions
:主要有定義和更新約束的方法,如mas_makeConstraints:View+MASAdditions
:除了定義和更新約束的一系列方法以外,還爲View增長了mas_top, mas_left等Attribute屬性
Core模塊就是Masonry的核心部分,Masonry的大部分功能都在這4個類裏實現
MASConstraintMaker
:約束控制器。控制更新,刪除,或者新增約束MASConstraint
:約束的基類,虛類。定義了Constraint的基本屬性和方法。MASViewConstraint
: 約束的主要實現類。全部對約束使用的功能均在此類中完成MASCompositeConstraint
:約束的集合類。內部有一個數組,能夠保存多個MASViewConstraint。對MASCompositeConstraint調用方法實際等於對其內部的全部MASViewConstraint調用方法
此模塊主要封裝了一些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);
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中
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作操做時候,其內部的全部屬性都會執行相應的操做
- (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賦值:
.equalTo(@[view1.mas_left,view2.mas_left])
,邏輯上確定不能是不等關係(>=,<=),因此realationship不用賦值,使用默認值(=)。copy出多個viewConstraint,將secondViewAttribute賦值。而後用多個viewConstraint組成的compositeConstraint替換調原來的viewConstraint。- (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)
看完了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方法很簡單,
上面的代碼咱們知道,關鍵的地方仍是在於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; }
其實真正關鍵的地方在[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]; } }
.equalTo(item.attribute)
中的item.attribute
。當咱們寫下相似make.left.offset(10);
約束的時候,是沒有secondViewAttribute的,這時候默認secondItem爲其父View,secontAttribute等於firstLayoutAttribute。這就解釋了爲何能夠這樣寫make.left.offset(10);
僅僅將代碼結構和基本實現過程解析了一下,更多實現細節還須要你們本身去閱讀源碼
說實話,Masonry的代碼寫得真漂亮,無論是代碼格式規範,仍是設計模式。看起來簡直是一種享受。建議你們閱讀。
Autolayout的第一次親密接觸也更新了一些東西。沒閱讀過或者閱讀時間比較早的朋友能夠再看看~
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 //從新生成約束,會將以前的全部約束先去掉
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; }
[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)
[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];
[view mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(view.superview); make.width.height.equalTo(@300); }];
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];//強制繪製 }];
借鑑@星光社的戴銘 的方法 [AutoLayout框架Masonry使用心得]
(http://www.jianshu.com/p/24e4ff56bfea)
也能夠參考forkingdog的FDTemplateLayoutCell
tableView.rowHeight = UITableViewAutomaticDimension; tableView.estimatedRowHeight = 80; //減小第一次計算量,iOS7後支持 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return UITableViewAutomaticDimension;//返回便可 }
//在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); }
[view.superview layoutIfNeeded];
以後會強制性更新約束,這句話以後即可以獲得frame,在iOS8中只須要在這加入設置contentSize即可以實現正常的scrollView滾動,而iOS7中則不能夠,請注意。
並且若是出現什麼疑難雜症的話,基本都是AutoLayout在iOS的不適用,因此搜索問題的話,各位直接搜索Autolayout 關鍵字即可,沒必要搜索Masonry關鍵字的問題(反正也搜不到什麼答案...)
[_contentLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
之因此開始寫blog,只由於,梳理blog的過程當中,會讓本身懂得更多。
看到比較好的blog, 在此附上, 向各位大神學習(串哥好像還出了直播講解masonry,等視頻上傳了去觀摩下)
裏脊串的開發隨筆 - 《Masonry介紹與使用實踐(快速上手Autolayout)》
裏脊串的開發隨筆 - 《如何使用Masonry設計複合型cell》
星光社的戴銘 - 《AutoLayout框架Masonry使用心得》
相關連接:
iOS開發筆記--使用Auto Layout中的VFL(Visual format language)--代碼實現自動佈局