這是 GitHub 上,Masonry 官方對 Masonry 的介紹:
Masonry is a light-weight layout framework which wraps AutoLayout with a nicer syntax. Masonry has its own layout DSL which provides a chainable way of describing your NSLayoutConstraints which results in layout code that is more concise and readable. Masonry supports iOS and Mac OS X.
譯文如下:
Masonry 是一個輕量級的布局框架,它通過一種友好的語法封裝了自動布局版确。Masonry 通過鏈?zhǔn)秸Z法 DSL(Domain-specific language) 來封裝 NSLayoutConstraints趋箩,使布局代碼更加地簡潔易讀矫户。Masonry 支持 iOS 和 Mac OS X微驶。
在分析 Masonry 源碼之前腮鞍,有必要先說一下 Masonry 的基本使用弛房,而在說 Masonry 的基本使用之前道盏,我們還是先來看看 storyboard 以及 xib 中是如何進行 AutoLayout 的,我截了兩張圖:
從上圖我們可以看出文捶,在 storyboard 和 xib 中荷逞,我們可以在可視化界面對控件進行 AutoLayout,操作較為簡單方便粹排。Masonry 的實現(xiàn)原理與這很相似种远,接下來我們就一起看看如何使用 Masonry 進行自動布局。
先看一下 Masonry 支持哪些屬性:
/**
* 以下屬性返回一個新的 MASViewConstraint
*/
@property (nonatomic, strong, readonly) MASConstraint *left; // 左側(cè)
@property (nonatomic, strong, readonly) MASConstraint *top; // 上側(cè)
@property (nonatomic, strong, readonly) MASConstraint *right; // 右側(cè)
@property (nonatomic, strong, readonly) MASConstraint *bottom; // 下側(cè)
@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; // 文本基線
/**
* 返回一個 block 對象顽耳,block 的接收參數(shù)是 MASAttribute 類型,返回一個 MASCompositeConstraint 對象
*/
@property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);
/**
* 返回一個 MASConstraint 對象坠敷,包含上下左右的布局信息
*/
@property (nonatomic, strong, readonly) MASConstraint *edges;
/**
* 返回一個 MASConstraint 對象,包含寬高的布局信息
*/
@property (nonatomic, strong, readonly) MASConstraint *size;
/**
* 返回一個 MASConstraint 對象射富,包含 centerX 和 centerY 信息
*/
@property (nonatomic, strong, readonly) MASConstraint *center;
有了屬性之后膝迎,怎樣添加約束呢?使用 Masonry 添加約束的函數(shù)有三個胰耗,這三個方法我們在文件 View+MASAdditions
中可以查看到:
/// 新增約束
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/// 更新約束
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/// 清除舊約束弄抬,只保留新約束
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
上面這三個方法中最常用的是 - (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block
。即第一個宪郊,接下來我們就嘗試一下掂恕。
舉個栗子:
導(dǎo)入 Masonry 框架之后,添加以下代碼:
- (void)layoutViews
{
UIImageView *imageView = [[UIImageView alloc] init];
[imageView setImage:[UIImage imageNamed:@"123"]];
[self.view addSubview:imageView];
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(100);
make.right.equalTo(self.view).offset(-100);
make.top.equalTo(self.view).offset(250);
make.bottom.equalTo(self.view).offset(-250);
}];
}
上面這幾句代碼便可以對 UIImageView 控件進行約束弛槐,這里還有兩點值得一提懊亡。第一點:下面這三種方式會實現(xiàn)相同的效果
/// 第一種
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(100);
make.right.equalTo(self.view).offset(-100);
make.top.equalTo(self.view).offset(250);
make.bottom.equalTo(self.view).offset(-250);
}];
/// 第二種
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.left.right.equalTo(self.view).insets(UIEdgeInsetsMake(250, 100, 250, 100));
}];
/// 第三種
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(250, 100, 250, 100));
}];
三種方式得出的效果圖一樣:
第二點:必須先把控件添加到視圖上,才能對控件進行布局乎串,否則程序會崩店枣。即先 addSubview:
,再 mas_makeConstraints:
叹誉。
</br>
以上是對 Masonry 使用的簡單介紹鸯两。接下來我們開始分析它的源碼。
通過對 Masonry 使用方法的了解长豁,我們可以看出 Masonry 的使用過程還是很簡潔的
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(100);
make.right.equalTo(self.view).offset(-100);
make.top.equalTo(self.view).offset(250);
make.bottom.equalTo(self.view).offset(-250);
}];
那我們就從 mas_makeConstraints:
這個方法開始探尋 Masonry 的源碼钧唐。上文說到,Masonry 中設(shè)置約束最常用的方法是
/// 新增約束
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
同時匠襟,Masonry 還提供兩個類方法用于更新和重建約束
/// 更新約束
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/// 清除舊約束钝侠,只保留新約束
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
這里我們就以 mas_makeConstraints:
為切入點開始分析 Masonry 這個框架。mas_makeConstraints:
這個方法位于分類View+MASAdditions
中酸舍,方法的實現(xiàn)如下:
/// 新增約束
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
/// 我們是手動添加約束帅韧,因此將自動轉(zhuǎn)換關(guān)閉
self.translatesAutoresizingMaskIntoConstraints = NO;
/// 創(chuàng)建 MASConstraintMaker 對象
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
/// 通過 block 進行值的回調(diào)
block(constraintMaker);
/// 調(diào)用 install 方法
return [constraintMaker install];
}
該方法先去掉 AutoResizing 的自動轉(zhuǎn)換(如果這個屬性沒有被正確設(shè)置,那么視圖的約束不會被成功添加)啃勉,接著初始化一個 MASConstraintMaker 對象忽舟,傳遞到 block 中,執(zhí)行 block淮阐,最后調(diào)用 install 方法叮阅。
第一次點擊進來看到這個方法之后,我有幾處疑問枝嘶。
- 第一帘饶,MASConstraintMaker 類內(nèi)部做了什么操作?
- 第二群扶,回調(diào)
block(constraintMaker)
有什么用及刻? - 第三,調(diào)用
[constraintMaker install]
方法實現(xiàn)了什么竞阐?
</br>
我們先來分析一下 MASConstraintMaker 這個類缴饭。MASConstraintMaker 是 Masonry 框架整個 DSL 過程的控制中心,它控制著整個添加過程骆莹,上文我們總結(jié) Masonry 支持哪些屬性時颗搂,總結(jié)的那些屬性就來自 MASConstraintMaker 類。我們知道 Masonry 是基于 AutoLayout 進行的封裝幕垦,所以接著我們一起來看下 MASConstraintMaker 是如何發(fā)揮作用的丢氢。下面是 MASConstraintMaker 的初始化
/// 這里的 MAS_VIEW 是一個宏傅联,#define MAS_VIEW UIView
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
從上邊代碼中我們可以清晰的看出,Masonry 在初始化 MASConstraintMaker 時疚察,將當(dāng)前的 view 賦給 MASConstraintMaker 類蒸走,并初始化一個 constraints 的空可變數(shù)組,作為約束數(shù)組貌嫡。
除此之外 MASConstraintMaker 還做了什么呢比驻?
先來到 MASConstraintMaker 的頭文件 MASConstraintMaker.h
中,下面是一些比較常規(guī)的屬性
/**
* 以下屬性返回一個新的 MASViewConstraint
*/
@property (nonatomic, strong, readonly) MASConstraint *left; // 左側(cè)
@property (nonatomic, strong, readonly) MASConstraint *top; // 上側(cè)
@property (nonatomic, strong, readonly) MASConstraint *right; // 右側(cè)
@property (nonatomic, strong, readonly) MASConstraint *bottom; // 下側(cè)
@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; // 文本基線
另外可以在 MASConstraintMaker 的 MASConstraintMaker.m
中看到另一個屬性 @property (nonatomic, strong) NSMutableArray *constraints
岛抄,用來存儲 MASConstraint
@interface MASConstraintMaker () <MASConstraintDelegate>
@property (nonatomic, weak) MAS_VIEW *view;
/// 存儲 MASConstraint
@property (nonatomic, strong) NSMutableArray *constraints;
@end
接下來我們通過下面這個例子來分析上面這些屬性:
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.left.right.equalTo(self.view).insets(UIEdgeInsetsMake(250, 100, 250, 100));
}];
- block 中首先會執(zhí)行
make.top
别惦,會先調(diào)用一個增加約束的通用方法- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
,接著會調(diào)用 MASConstraintMaker 中 MASConstraintDelegate 的- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
方法夫椭。具體代碼如下:
/// 重寫 getter 方法
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
/// 增加約束的通用方法
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
/// 通過 NSLayoutAttribute 添加約束
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
/// 構(gòu)造 MASViewAttribute
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
/// 通過 MASViewAttribute 構(gòu)造第一個 MASViewConstraint
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
/// 如果存在 contraint掸掸,則把 constraint 和 newConstraint 組合成 MASCompositeConstraint
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
/// 替換原來的 constraint 成新的 MASCompositeConstraint
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
/// 不存在則設(shè)置 constraint 到 self.constraints
if (!constraint) {
/// 設(shè)置delegate
newConstraint.delegate = self;
/// 將約束添加到self.constraints
[self.constraints addObject:newConstraint];
}
/// 返回剛剛創(chuàng)建的 MASViewConstraint 對象
return newConstraint;
}
值得一提的是,當(dāng)調(diào)用 make.top
的時候會創(chuàng)建一個只有 firstViewAttribute 的 MASViewConstraint 對象益楼,并且進入不存在 constraint 的代碼部分猾漫,詳情見代碼塊中的注釋。
/// 不存在則設(shè)置 constraint 到 self.constraints
if (!constraint) {
/// 設(shè)置delegate
newConstraint.delegate = self;
/// 將約束添加到self.constraints
[self.constraints addObject:newConstraint];
}
/// 返回剛剛創(chuàng)建的 MASViewConstraint 對象
return newConstraint;
在這個方法的實現(xiàn)過程中感凤,make.top
的返回值是 MASViewConstraint 對象悯周。
- 當(dāng)執(zhí)行到
make.top.bottom
的時候,其實是對 MASViewConstraint 對象 .bottom 的調(diào)用陪竿,會走到 MASViewConstraint 中重寫的- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
方法禽翼,然后最終還是會調(diào)用這個代理方法- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
/// 通過 NSLayoutAttribute 添加約束
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
/// 構(gòu)造 MASViewAttribute
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
/// 通過 MASViewAttribute 構(gòu)造第一個 MASViewConstraint
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
/// 如果存在 contraint,則把 constraint 和 newConstraint 組合成 MASCompositeConstraint
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
/// 替換原來的 constraint 成新的 MASCompositeConstraint
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
/// 不存在則設(shè)置 constraint 到 self.constraints
if (!constraint) {
/// 設(shè)置delegate
newConstraint.delegate = self;
/// 將約束添加到self.constraints
[self.constraints addObject:newConstraint];
}
/// 返回剛剛創(chuàng)建的 MASViewConstraint 對象
return newConstraint;
}
和前面的執(zhí)行過程不同族跛,因為 MASViewConstraint 的 delegate 對象是剛才設(shè)置過的 MASConstraintMaker 對象闰挡,并且因為 constraint 不是 nil,所以會進入
/// 如果存在 contraint礁哄,則把 constraint 和 newConstraint 組合成 MASCompositeConstraint
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
/// 替換原來的 constraint 成新的 MASCompositeConstraint
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
因此調(diào)用 make.top.bottom 返回的是一個 MASCompositeConstraint 對象长酗。
- 當(dāng)程序執(zhí)行到
make.top.bottom.left
時,就是對 MASCompositeConstraint 中 .left 的調(diào)用桐绒,會走 MASCompositeConstraint 中的重寫方法- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
夺脾,接著會調(diào)用- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
方法,最終還是會調(diào)用- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
茉继。代碼如下:
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
return self;
}
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
id<MASConstraintDelegate> strongDelegate = self.delegate;
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
newConstraint.delegate = self;
[self.childConstraints addObject:newConstraint];
return newConstraint;
}
/// 通過 NSLayoutAttribute 添加約束
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
/// 構(gòu)造 MASViewAttribute
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
/// 通過 MASViewAttribute 構(gòu)造第一個 MASViewConstraint
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
/// 如果存在 contraint咧叭,則把 constraint 和 newConstraint 組合成 MASCompositeConstraint
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
/// 替換原來的 constraint 成新的 MASCompositeConstraint
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
/// 不存在則設(shè)置 constraint 到 self.constraints
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
但這次 if ([constraint isKindOfClass:MASViewConstraint.class])
與 if (!constraint)
都不會進入,只直接返回 MASViewConstraint 對象烁竭,然后回到 - (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
方法中設(shè)置它的 delegate菲茬,并且將對象存入 MASCompositeConstraint 的 childConstraints 中。
之后再有更多的鏈?zhǔn)?MASConstraint 的組合(比如執(zhí)行到
make.top.bottom.left.right
),也只是 MASCompositeConstraint 的調(diào)用婉弹,直接加入 childConstraints 中即可睬魂。至于
equalTo(self.view)
的調(diào)用過程,這里有必要說明一下马胧,equalTo(self.view)
在文件 MASConstraint 中執(zhí)行- (MASConstraint * (^)(id))equalTo
方法汉买,接著調(diào)用 MASViewConstraint 中的- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation
方法。代碼如下:
/// MASConstraint 是一個抽象類佩脊,其中有很多的方法都必須在子類中覆寫。Masonry 中有兩個 MASConstraint 的子類垫卤,分別是 MASViewConstraint 和 MASCompositeConstraint
/// MASConstraint.m
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
/// MASViewConstraint.m
- (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.layoutRelation = relation;
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;
}
};
}
- (MASConstraint * (^)(id))equalTo
方法提供了參數(shù) attribute 和布局關(guān)系 NSLayoutRelationEqual威彰,這兩個參數(shù)會傳遞到 - (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation
中,設(shè)置 constraint 的布局關(guān)系和 secondViewAttribute 屬性穴肘,為 [constraintMaker install]
做準(zhǔn)備歇盼。
</br>
到這里基本上就把上文的那個例子說完了,通過例子讓我們對 MASConstraintMaker 中的一些常規(guī)屬性有了一定的了解评抚,同時也明白了 block(constraintMaker)
這個方法的作用——在調(diào)用 block(constraintMaker)
時豹缀,對 constraintMaker 進行配置。 MASConstraintMaker 中還有一些別的屬性慨代,我們再一起來看看吧
/**
* 返回一個 block 對象邢笙,block 的接收參數(shù)是 MASAttribute 類型,返回一個 MASCompositeConstraint 對象
*/
@property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);
/**
* 返回一個 MASConstraint 對象侍匙,包含上下左右的布局
*/
@property (nonatomic, strong, readonly) MASConstraint *edges;
/**
* 返回一個 MASConstraint 對象氮惯,包含寬高的布局
*/
@property (nonatomic, strong, readonly) MASConstraint *size;
/**
* 返回一個 MASConstraint 對象,包含 centerX 和 centerY
*/
@property (nonatomic, strong, readonly) MASConstraint *center;
這里只簡單的介紹一下上面的幾個屬性
attributes
:返回一個 block 對象想暗,block 的接收參數(shù)是 MASAttribute 類型妇汗,返回 MASCompositeConstraint 對象
-
edges
:返回一個 MASConstraint 對象,同時包含了上下左右的布局 -
size
:返回一個 MASConstraint 對象说莫,同時包含了寬高的布局 -
center
:返回一個 MASConstraint 對象杨箭,同時包含了 centerX 和centerY
</br>
接下來我們來分析 [constraintMaker install]
方法。
我們在- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block
方法的最后會調(diào)用 [constraintMaker install]
方法來添加所有存儲在 self.constraints
數(shù)組中的所有約束储狭。
- (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];
}
/// 去除所有緩存的約束結(jié)構(gòu)體
[self.constraints removeAllObjects];
return constraints;
}
這個方法會先判斷當(dāng)前視圖的約束是否需要刪除互婿,如果我們之前調(diào)用過 - (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block
這個方法(它會把 removeExisting 的 BOOL 值設(shè)為 YES),那么視圖中的原有約束就會被全被刪除晶密。接著往下走需了,程序會遍歷 constraints 數(shù)組茸炒,發(fā)送 install 消息。
/// MASViewConstraint.m
- (void)install {
/// 已經(jīng)有約束
if (self.hasBeenInstalled) {
return;
}
/// 支持 active 且已經(jīng)有約束
if ([self supportsActiveProperty] && self.layoutConstraint) {
/// 激活約束
self.layoutConstraint.active = YES;
/// 添加約束緩存
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
/// 獲得 firstLayoutItem, firstLayoutAttribute, secondLayoutItem, secondLayoutAttribute
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
// alignment attributes must have a secondViewAttribute
// therefore we assume that is refering to superview
// eg make.left.equalTo(@10)
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
/// NSLayoutConstraint 的創(chuàng)建,生成約束蝙茶,MASLayoutConstraint 其實就是 NSLayoutConstraint 的別名
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
/// 設(shè)置 priority 和 mas_key
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
/// 如果 secondViewAttribute 有 view 對象
if (self.secondViewAttribute.view) {
/// 取得兩個 view 的最小公共 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);
/// 設(shè)置約束 view 為此 view
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
self.installedView = self.firstViewAttribute.view;
} else {
self.installedView = self.firstViewAttribute.view.superview;
}
/// 已經(jīng)存在的約束
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];
}
}
上面這個方法是為當(dāng)前視圖添加約束的最后的方法有序,首先這個方法會先獲取即將用于初始化 NSLayoutConstraint 的子類的幾個屬性
/// MASViewConstraint.m
/// 獲得 firstLayoutItem, firstLayoutAttribute, secondLayoutItem, secondLayoutAttribute
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
然后判斷當(dāng)前即將添加的約束,如果不是 size 類型并且沒有提供 self.secondViewAttribute,會自動將約束添加到 superview 上
/// MASViewConstraint.m
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
接著創(chuàng)建 MASLayoutConstraint 對象
/// MASViewConstraint.m
/// NSLayoutConstraint 的創(chuàng)建畜侦,生成約束,MASLayoutConstraint 其實就是 NSLayoutConstraint 的別名
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
/// 設(shè)置 priority 和 mas_key
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
創(chuàng)建完約束對象后躯保,我們要尋找該約束添加到那個 View 上旋膳。下方的代碼段就是獲取接收該約束對象的視圖。如果是兩個視圖相對約束途事,就獲取兩種的公共父視圖验懊。如果添加的是 Width 或者 Height,那么就添加到當(dāng)前視圖上尸变。如果既沒有指定相對視圖义图,也不是 Size 類型的約束,那么就將該約束對象添加到當(dāng)前視圖的父視圖上召烂。代碼實現(xiàn)如下:
/// MASViewConstraint.m
/// 如果 secondViewAttribute 有 view 對象
if (self.secondViewAttribute.view) {
/// 取得兩個 view 的最小公共 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);
/// 設(shè)置約束 view 為此 view
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
self.installedView = self.firstViewAttribute.view;
} else {
self.installedView = self.firstViewAttribute.view.superview;
}
加約束時我們要判斷是否需要對約束進行更新碱工,如果需要,就替換約束奏夫,如果不需要就直接添加約束即可怕篷。添加成功后我們將通過 mas_installedConstraints 屬性記錄一下本次添加的約束。
/// 已經(jīng)存在的約束
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];
}
</br>
到此為止酗昼,對 Masonry 源碼的簡單介紹已接近尾聲了廊谓。。仔雷。Masonry 的代碼流程簡單來講就是提供給我們一個 MASConstraintMaker蹂析,然后我們根據(jù) Masonry 提供的語法,添加約束碟婆。最后 Masonry 解析約束电抚,將真正的約束關(guān)系添加到相應(yīng)的視圖上。