Masonry這個框架是使用程式碼進行自動佈局使用的,它的使用非常廣泛,這段時間一直在學習這個框架,因此想把學到的東西記下來,方便以後查閱,也便於與人分享。
自動佈局約束的等式:
item1.attribute1 = multiplier × item2.attribute2 + constant
Masonry中使用了大量的點鏈式語法,考慮到應該有些小夥伴不知道點鏈式語法的來龍去脈,因此這裡先整理一下點鏈式語法。
點鏈式語法
我們先來看一下Masonry框架的一種使用:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(superview.mas_left).mas_offset(30);
}];
複製程式碼
上面的程式碼是Masonry的簡單的使用,這裡面就用到了點鏈式語法make.left.equalTo(superview.mas_left).mas_offset(30);
,我們看一下這句點鏈式語法,這裡麵包括三個要素:
- 點語法:我們在訪問屬性的時候會使用點語法。
- 小括號呼叫:在Objective-C中使用[ ]來呼叫方法,只有在呼叫Block的時候會使用(),因此這裡我們可以使用Block來實現點鏈式語法中的()。
- 連續呼叫:Block是有返回值的,那麼我們可以在每次呼叫完Block後返回撥用者物件本身,那麼我們就可以實現連續的呼叫了。 總結起來就是:
我們可以宣告一些Block型別的屬性,讓block型別的屬性的返回值為其本身。
下面用一個計算器的例子來說明一下:
//Calculator.h
@interface Calculator : NSObject
//這裡是建立一個屬性,屬性的型別是block型別,屬性名是add
@property (nonatomic, copy)Calculator * (^add)(NSInteger num);
@property (nonatomic, copy)Calculator * (^minus)(NSInteger num);
@property (nonatomic, copy)Calculator * (^multiply)(NSInteger num);
@property (nonatomic, copy)Calculator * (^divide)(NSInteger num);
@property (nonatomic, assign)NSInteger result;
@end
複製程式碼
//Calculator.m
@implementation Calculator
- (instancetype)init
{
self = [super init];
if (self) {
self.result = 0;
}
return self;
}
//這裡實現的是add這個屬性的get方法,只不過屬性的型別是block型別的。
- (Calculator * (^)(NSInteger num))add
{
return ^id(NSInteger num){
self.result += num;
return self;
};
}
- (Calculator * (^)(NSInteger num))minus
{
return ^id(NSInteger num){
self.result -= num;
return self;
};
}
- (Calculator * (^)(NSInteger num))multiply
{
return ^id(NSInteger num){
self.result *= num;
return self;
};
}
- (Calculator * (^)(NSInteger num))divide
{
return ^id(NSInteger num){
self.result /= num;
return self;
};
}
@end
複製程式碼
呼叫:
Calculator *calculator = [[Calculator alloc] init];
calculator.add(5).minus(8).multiply(8).divide(23);
複製程式碼
- 1.calculator.add是呼叫了add屬性的get方法,這個方法會返回一個block,block如下:
return ^id(NSInteger num){
self.result += num;
return self;
};
複製程式碼
- 2.calculator.add(5)會執行這個block,這個block的返回值是Calculator物件本身,所以calculator.add(5)執行完了得到的是一個Calculator物件。
- 3.Calculator物件繼續訪問minus屬性,執行minus屬性的get方法。 #####更簡潔的實現 上面是通過宣告一系列的block型別的屬性,再實現block屬性的get方法來實現鏈式呼叫,但是Masonry的實現方式和這種方式還是有區別,我們在Masonry中並沒有發現Block型別的屬性的宣告,反而是看到了一些平時見的比較少的方法的宣告:也就是說Masonry中是把Block型別的屬性改成了返回值為Block型別的方法,這樣也能成功實現鏈式語法,這是為什麼呢? 回想一下,當我們通過點語法去訪問屬性的時候實質上就是訪問了get方法,那麼當不存在一個名為name的屬性時,我們使用self.name去訪問時是不是也會跑去執行名為name的方法呢?答案是肯定的,也就是隻要我們申明瞭一個xxx方法,那就可以放心的寫self.xxx。 所以最終Calculator.h檔案就改成了這樣:
@interface Calculator : NSObject
/*
@property (nonatomic, copy)Calculator * (^add)(NSInteger num);
@property (nonatomic, copy)Calculator * (^minus)(NSInteger num);
@property (nonatomic, copy)Calculator * (^multiply)(NSInteger num);
@property (nonatomic, copy)Calculator * (^divide)(NSInteger num);
@property (nonatomic, assign)NSInteger result;
*/
- (Calculator * (^)(NSInteger num))add;
- (Calculator * (^)(NSInteger num))minus;
- (Calculator * (^)(NSInteger num))multiply;
- (Calculator * (^)(NSInteger num))divide;
@end
複製程式碼
Masonry的使用方法
1.使用MASConstraintMaker建立約束
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
複製程式碼
或者更簡單的方法:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
複製程式碼
並不是只有equalTo即等於這一種關係,還可以有:
lessThanOrEqualTo:等同於NSLayoutRelationLessThanOrEqual greaterThanOrEqualTo:等同於NSLayoutRelationGreaterThanOrEqual
2.MASViewAttribute
Masonry中有MASViewAttribute這個類,這個類就等同於NSLayoutAttribute這個類:
MASViewAttribute | NSLayoutAttribute |
---|---|
view.mas_left | NSLayoutAttributeLeft |
view.mas_right | NSLayoutAttributeRight |
view.mas_top | NSLayoutAttributeTop |
view.mas_bottom | NSLayoutAttributeBottom |
view.mas_leading | NSLayoutAttributeLeading |
view.mas_trailing | NSLayoutAttributeTrailing |
view.mas_width | NSLayoutAttributeWidth |
view.mas_height | NSLayoutAttributeHeight |
view.mas_centerX | NSLayoutAttributeCenterX |
view.mas_centerY | NSLayoutAttributeCenterY |
view.mas_baseline | NSLayoutAttributeBaseline |
3.與常數有關的問題
自動佈局不允許對齊的屬性如left,right,centerY等設定為常數,如果我們傳了一個常數給這些屬性,Masonry會自動把這些約束變為相對於父檢視的約束,即:
//creates view.left = view.superview.left + 10
make.left.equalTo(@10)
複製程式碼
4.mas字首相關
在使用Masonry的時候,有時候會比較迷糊什麼時候使用帶有mas字首的,什麼時候使用不帶字首的,我們看下面這句程式碼:
make.top.mas_equalTo(42);
複製程式碼
這句程式碼也可以這樣寫:
make.top.equalTo(@42);
複製程式碼
但是這樣寫就會報錯:
make.top.equalTo(42);
複製程式碼
原因就在於這個括號裡面的引數型別必須是id型別,如果括號裡面的引數不傳id型別就傳常量型別也行,那麼就必須要在equalTo前面加上mas,加上mas後,mas_equalTo會把傳進來的數值型別變成id型別。
5.MASCompositeConstraint類相關
Masonry給了我們幾個便利的方法來讓我們一次性建立多個約束,Masonry中與這個約束相關的類是MASCompositeConstraint類,簡單使用如下: edges
// make top, left, bottom, right equal view2
make.edges.equalTo(view2);
// make top = superview.top + 5, left = superview.left + 10,
// bottom = superview.bottom - 15, right = superview.right - 20
make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))
複製程式碼
size
// make width and height greater than or equal to titleLabel
make.size.greaterThanOrEqualTo(titleLabel)
// make width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
複製程式碼
center
// make centerX and centerY = button1
make.center.equalTo(button1)
// make centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
複製程式碼
6.修改已經存在的約束
當我們只是修改約束的constant的時候,可以使用mas_updateConstraints
:
// this is Apple's recommended place for adding/updating constraints
// this method can get called multiple times in response to setNeedsUpdateConstraints
// which can be called by UIKit internally or in your code if you need to trigger an update to your constraints
- (void)updateConstraints {
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
複製程式碼
當我們要修改的不止是約束的constant的時候,使用mas_updateConstraints
就力不從心了,這時就需要使用mas_remakeConstraints
:
- (void)changeButtonPosition {
[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);
}
}];
}
複製程式碼
解讀原始碼
我們在解讀原始碼的時候先從最簡單最基礎的使用開始,然後由淺入深,逐漸深入。下面我們先分析一下整個框架的檔案結構:
下面就從一個最簡單最基本的使用開始來探究原始碼: [view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(superview.mas_left).mas_offset(30);
}];
複製程式碼
我們先不管外面的方法呼叫,只需要知道make是MASConstraintMaker型別的就行了,從make.left.equalTo(superview.mas_left).mas_offset(30);
開始:
- 1.
make.left
left是它的一個屬性,這裡呼叫的是屬性的getter方法。繼續往下檢視:再繼續: 我們來看一下MASViewConstraint物件的建立:
那麼現在我們來總結一下make.left做了哪些事:
make.left是呼叫了MASConstraintMaker類的left屬性的get方法,這裡建立了一個MASViewAttribute物件,這個物件由一個UIView物件和一個NSLayoutAttribute來建立,這裡UIView物件是view,NALayoutAttribute為NSLayoutAttributeLeft,所以這裡MASViewAttribute物件也就是封裝了約束等號左邊的兩個元素。。然後使用建立的MASViewAttribute物件來建立了一個MASViewConstraint物件,這個物件代表這一行程式碼所表示的整個約束。最終make.left返回了一個MASVIewConstraint物件。
需要注意的是,MASConstraintMaker
物件有一個陣列型別的consrtaints
屬性,新建立的MASViewConstraint物件被加入到了這個屬性中,在最後新增約束的時候會遍歷這個陣列。
- 2.
superview.mas_left
mas_left是分類的一個屬性,所以superview.mas_left會呼叫分類的-(MASViewAttribute *)mas_left
方法。 這裡通過程式碼建立了一個MASViewAttribute物件,物件的view即superview,物件的attribute即NSLayoutAttributeLeft,我們看看是如何建立的: 總結一下:
superview.mas_left返回了一個MASViewAttribute物件,這個物件封裝了約束等號右邊的兩個元素。
- 3.
make.left.equalTo(superview.mas_left)
進入equalTo檢視具體實現:也就是說我執行make.left.equalTo
會得到一個Block,那麼我執行make.left.equalTo(superview.mas_left)
就是執行這個Block,即make.left.equalTo(superview.mas_left)
會執行self.equalToWithRelation(attribute, NSLayoutRelationEqual)
這一行核心程式碼,並返回這一行核心程式碼的返回值。 由於make.left是MASViewConstraint物件,所以我們要去MASViewConstraint類中檢視equalWithRelation的實現:self.secondViewAttribute = attribute;
會觸發secondViewAttribute
這個屬性的set方法,我們看一下其set方法的實現: 這裡的意思就是make.left.equalTo()
這個括號裡面傳入的東西可能有三種情況,第一種是數字,第二種是一個UIView物件,如果是UIView物件,那就將其layoutAttribute設定為何firstAttribute一致,也就是我們也可以這樣寫:make.left.equalTo(superview)
,這樣Masonry也能成功識別。第三種是傳入的MASViewConstraint物件,如果傳入這種物件則可以直接賦值給secondViewAttribute屬性。 下面我們再來看一下第一種情況傳入數字的處理方式,我們進入setLayoutConstantWithValue:
這個方法: 程式碼裡面設定offset,centerOffset等都不是真正的實現,真正的實現在offset屬性的set方法裡,我們要其MASViewConstraint類中找offset屬性的set方法: 總結起來就是如果傳入的是數值型別,那麼就給MASViewConstraint的layoutConstant屬性賦值。
這樣我們就清楚了
make.left.equalTo()
這個括號中傳入各種不同型別的值會怎麼操作。
總結一下make.left.equalTo(superview.mas_left)
做的事情:
make.left
建立了一個firstViewAttribute
,firstViewAttribute
的view屬性即為mas_makeConstraint
方法的呼叫者,其layoutAttribute
屬性為NSLayoutAttributeLeft
,firstViewAttribute
封裝了約束等式左邊的兩個item。接著通過傳入firstViewAttribute
建立了一個MASViewConstraint
物件。superview.mas_left
則是建立了一個secondViewAttribute
物件,該物件的view即為superview,layoutAttribute
為NSLayoutAttributeLeft
。make.left.equalTo(supervie.mas_left)
則是將secondViewAttribute
賦值給MASViewConstraint
物件的secondViewAttribute
屬性,並給MASViewConstraint
物件的layoutRelation
屬性賦值。
- 4.
.mas_offset(30)
mas_offset
的顏色是土黃色,說明這是一個巨集定義,我們點進去,發現這個巨集定義是定義在MASConstraint.h
檔案中:
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
複製程式碼
這不是一個簡單的巨集定義,裡面還進行了巢狀,我們看一下MASBoxValue()
方法做了什麼,在MASUtilities.h
這個檔案中找到了MASBoxValue()
這個方法:
在Masonry的用法這部分我說過mas字首的使用,這裡我們就看到了其實現方法。如果我們括號裡想要直接傳入數值型別而不是id型別的引數,那麼前面使用的API就必須帶mas字首,否則報錯,如果傳入的是id型別,則前面使用的API是否帶mas字首均可。
所以.mas_offset(30)
也就等於.valueOffset(@30)
,那麼我們來檢視一下MASConstraint.m
中的實現:
MASViewConstraint
的layoutConstant屬性:
到這裡make.left.equalTo(superview.mas_left).mas_offset(30);
這行程式碼就全解讀完了。總結一下就是:
make,left
建立了一個MASViewConstraint
物件,為這個物件的firstViewAttribute
屬性賦值,superview.mas_left
即建立了一個MASViewAttribute
物件,equalTo()
即把這個MASViewAttribute
物件賦值給MASViewConstraint
物件的secondViewAttribute
屬性,.mas_offset(30)
則是給MASViewConstraint
物件的layoutConstant
屬性賦值為30.
- 5.
mas_makeConstraints:
mas_makeConstraints:
這個方法是在UIView的分類中定義並實現的,下面我們看一下其具體實現: 再來看[constraint install]:
,這個方法的內容比較多,我們分兩部分來看: 到這裡約束就新增完成了。 總結一下新增約束的大體流程:
首先根據
MASViewConstraint
物件的firstViewAttribute
和secondViewAttribute
這兩個屬性,訪問這兩個屬性得到firstLayoutItem
,firstLayoutAttribute
。secondLayoutitem
,secondLayoutAttribute
。然後處理有時是對齊屬性如left沒有提供view的情況,這時就要設定view為其父檢視。然後尋找應該將約束新增到哪個檢視上,最後新增約束到對應的檢視上。
組合約束(MASCompositeConstraint)
Masonry中可以直接約束size,center,edge這樣的組合約束。其本質也就是把它拆成多個單個約束,比如對size的約束,就是拆成width和height這兩個約束。下面我們看一下其具體的實現方法:
make.size.equalTo(superview).sizeOffset(CGSizeMake(10, 10));
複製程式碼
- 1.
make.size
繼續往下看:- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs
方法: 總結一下make.size
做了哪些事:
把size這一個拆分成了width和height這兩個,根據這兩個約束建立了兩個
MASViewConstraint
物件,裝到一個陣列裡面,使用這個陣列來建立了一個MASCompositeConstraint
物件,最後返回這個MASCompositeConstraint
物件。
- 2.
.equalTo(superview)
繼續: 總結一下make.size.equalTo(superview)
:
把size這一個約束拆分成了width和height這兩個約束,並根據此建立了兩個
MASViewConstraint
物件,根據這兩個物件組成的陣列去建立一個MASCompositeConstraint
物件。然後遍歷MASCompositeConstraint
物件的childConstraints
陣列,取出陣列裡面的額每個MASViewConstraint
物件,然後像處理單個MASViewConstraint
一樣去處理。
- 3.
.sizeOffset(CGSizeMake(10, 10))
可以想象就是把它拆分,然後賦值給兩個MASViewConstraint的layoutConstant屬性,就不詳細說了。
mas_updateConstraints:和mas_remakeConstraints:
自動佈局約束等式: item1.attribute1 = multiplier × item2.attribute2 + constant
有時候我們有更改約束的需求,比如我們要做一個動畫,移動某個檢視,那就需要改變檢視約束,當我們只是改變約束的constant時,可以使用
mas_updateConstraints:
這個方法。而當我們需要改變的不止constant時,就需要呼叫mas_remakeConstraints:
這個方法了。
先來看一下mas_updateConstraints:
這個方法是怎麼實現只改變約束的constant的:
[constraintMaker install]
看看[constraint install]
中是怎麼實現的:
總結起來,
mas_updateConstraints:
就是去self.installedView.constarints
這個屬性陣列中去遍歷,看有沒有這樣一個NSLayoutConstraint
物件,它除了constant外,所有內容都和當前的NSLayoutConstraint
物件一致,如果有這個物件,那麼就把該物件的constant改為當前NSLayoutConstraint
物件的constant。這樣來完成約束條件的更新。
再來看一下mas_remakeConstraints:
當我們要改變的約束條件不止是constant這麼簡單時,使用mas_remakeConstraints:
就不頂用了,就要使用mas_remakeConstraints:
這個方法。
[constraintMaker install]
:
這篇文章在簡書的地址:www.jianshu.com/p/8990c5a98…