追求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更多內容見 Autolayout的第一次親密接觸 )。make.edges.equalTo(view.superview).insets(edge);就是添加約束的過程。
對于一個約束。他實際表示的是一個不等或者相等關系
用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) 常數為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總體來說分為三大類
- Basic Attribute: 基本屬性,支持到iOS6,一般使用得比較多
- Margin Attribute: 邊緣相關屬性,支持到iOS8。由于版本要求比較高,一般用得比較少。Margin相關的詳細內容請參考 iOS8上關于UIView的Margin新增了3個APIs
- 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種不同類的屬性)
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....
小技巧
- 如果等式2邊的Attribute是一樣的,我們可以省略等式右邊的Attribute
- 如果是等于關系,并且右邊的view是父View。連equalTo也可以省略
- 如果equalTo里面傳的是NSValue類型,效果跟設置offset是一樣的
- 如果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);
撥開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+MASShorthandAdditions和View+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);
一、執行"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中
二、執行".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做操作時候,其內部的所有屬性都會執行相應的操作
三、執行".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賦值:
- 如果不是數組,直接對realationship和secondViewAttribute賦值
- 如果是數組,如:.equalTo(@[view1.mas_left,view2.mas_left]),邏輯上肯定不能是不等關系(>=,<=),所以realationship不用賦值,使用默認值(=)。copy出多個viewConstraint,將secondViewAttribute賦值。然后用多個viewConstraint組成的compositeConstraint替換調原來的viewConstraint。
四、執行".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方法很簡單,
- 將self.translatesAutoresizingMaskIntoConstraints至為NO。translatesAutoresizingMaskIntoConstraints表示是否將設置的Frame轉化為約束。當自己設置約束的時候需要將其置為NO
- 創建出MASConstraintMaker對象
- 通過block拋出到外面設值
- 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; }
- 如果需要removeExisting,就把已有的約束remove掉,當調用mas_remakeConstraints的時候會將removeExisting值置為YES
- 遍歷constraints,調用[constraint install]
- 清空constraints,這里的constraintMaker只是一個零時屬性,只是一個工具類,不需要存儲。所以用完之后就可以將constraints清空
其實真正關鍵的地方在[constraint install]
七、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]; } }
- 如果已經installed就不做任何操作
- 從ViewAttribute中剝離出item和attribute。前面我們介紹過MASViewAttribute的類主要是將item和attribute2個屬性封裝在了一起。
- secondViewAttribute的值來自于.equalTo(item.attribute)中的item.attribute。當我們寫下類似make.left.offset(10);約束的時候,是沒有secondViewAttribute的,這時候默認secondItem為其父View,secontAttribute等于firstLayoutAttribute。這就解釋了為什么可以這樣寫make.left.offset(10);
- 創建真正用于Autolayout的約束layoutConstraint
- 將priority和key賦值
- 找到要添加約束的installView。如果是2個View之間的約束,需要尋找這2個View最接近的共同父View。添加約束
- 添加或更新約束。當調用mas_updateConstraints的時候updateExisting=YES。這時候會查找是否有已經存在的約束。有就更新,沒有就添加。如果是mas_makeConstraints或mas_remakeConstraints,則直接添加
Extension
僅僅將代碼結構和基本實現過程解析了一下,更多實現細節還需要大家自己去閱讀源碼
說實話,Masonry的代碼寫得真漂亮,不管是代碼格式規范,還是設計模式。看起來簡直是一種享受。建議大家閱讀。
Autolayout的第一次親密接觸 也更新了一些東西。沒閱讀過或者閱讀時間比較早的朋友可以再看看~