前言
iOS 開發中的佈局方式,總體而言經過了三個時代。混沌初開之時,世間只有3.5英寸(iPhone 4、iPhone 4S),那個時候螢幕適配對於大多數 iOS 開發者來說並不是什麼難題,用 frame 就能精確高效的定位。這之後,蘋果釋出了4英寸機型(iPhone 5、iPhone 5C、iPhone 5S),與此同時蘋果也推出了 AutoresizingMask,用來協調子檢視與父檢視之間的關係。再之後,各種各樣的 iPhone 和 iPad 紛紛面世,不僅僅是螢幕尺寸方面的差異,更有異形屏(iPhone X)。在此期間,蘋果提出了 AutoLayout 技術,供開發者進行螢幕適配。
使用 AutoLayout
的方法也有兩種——通過 Interface Builder
或者純程式碼。前者一直是蘋果官方文件裡所鼓勵的,原因是蘋果從最初到現在,對於 iOS 應用的想法都是小而美的,在他們的認知裡,一個 APP 應該提供儘可能小的功能集,這也是為為何蘋果迄今為止官方推薦的架構仍然是 MVC,官方推薦的開發方式仍是以 StoryBoard(Size Classes)
。但是在一些專案較大的公司,StoryBoard
的某些特性(導致應用包過大,減緩啟動速度,合併程式碼困難)又是不能為人所容忍的,便有了純程式碼來實現 View
層的一群開發者(比如我)。
如果你曾經用程式碼來實現 AutoLayout
,你會發現蘋果提供的 API
的繁瑣程度令人髮指,這也是 Masonry
這類框架被發明的原因。Masonry
是一個輕量級的佈局框架,它使用更好的語法來封裝 AutoLayout
。Masonry
有自己的佈局 DSL
,它提供了一種鏈式的方式來描述你的 NSLayoutConstraints
,從而得到更簡潔和可讀的佈局程式碼。
接下來,我們就從 Masonry
中 README
提供的程式碼著手,看一看 Masonry
是如何幫助我們簡化繁瑣的 AutoLayout
程式碼的。
mas_makeConstraints:
舉一個簡單的例子,你想要一個檢視填充它的父檢視,但是在每一邊間隔10個點。
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
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);
}];
複製程式碼
我們想要實現約束的效果,是通過 mas_makeConstraints:
這個方法來實現的,這個方法可以在任意 UIView
類及其子類上呼叫,說明其是一個分類方法,這也是這個方法加了 mas_
字首的原因。該方法宣告在 UIView+MASAdditions.h
檔案中,先來看一下這個方法的完整宣告:
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
複製程式碼
這個方法傳遞的引數是一個引數為 MASConstraintMaker
型別的無返回值的 block
,而該方法的返回值則是一個陣列。方法宣告中我們看到了一個叫做 NS_NOESCAPE
的巨集,NS_NOESCAPE
用於修飾方法中的 block
型別引數,作用是告訴編譯器,該 block
在方法返回之前就會執行完畢,而不是被儲存起來在之後的某個時候再執行。編譯器被告知後,就會相應的進行一些優化。更詳細的內容請參考 Add @noescape to public library API
接下來是方法的實現:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
複製程式碼
self.translatesAutoresizingMaskIntoConstraints = NO;
預設情況下,view
上的 autoresizing mask
會產生約束條件,以完全確定檢視的位置。這允許 AutoLayout
系統跟蹤其佈局被手動控制的 view
的 frame
(例如通過 setFrame:
)。當你選擇通過新增自己的約束來使用 AutoLayout
來定位檢視時,必須 self.translatesAutoresizingMaskIntoConstraints = NO;
。IB 會自動為你做這件事。
constraintMaker
在這之後,創造了一個 MASConstraintMaker
型別的物件 constraintMaker
,MASConstraintMaker
的初始化方法為:
- (id)initWithView:(MAS_VIEW *)view;
複製程式碼
可以看到,傳入的 view
引數是由一個叫做 MAS_VIEW
的巨集來作為引數宣告的,MAS_VIEW
的定義是:
#if TARGET_OS_IPHONE || TARGET_OS_TV
#import <UIKit/UIKit.h>
#define MAS_VIEW UIView
...
#elif TARGET_OS_MAC
#import <AppKit/AppKit.h>
#define MAS_VIEW NSView
...
#endif
複製程式碼
因為 Masonry
是一個跨平臺的框架,於是通過預編譯巨集來讓在不同的平臺上,MAS_VIEW
代表的意義不同。接下來看初始化方法的實現:
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
複製程式碼
在初始化方法中,將傳入的 view
通過一個弱指標(@property (nonatomic, weak) MAS_VIEW *view;
)保留在了 constraintMaker
中,同時初始化了一個名為 constraints
的 NSMutableArray
,用來儲存約束。
block(constraintMaker);
接著,constraintMaker
通過 block(constraintMaker);
傳遞給了我們,而我們對它做了什麼呢?
make.top.equalTo(superview.mas_top).with.offset(padding.top);
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);
複製程式碼
我們將對檢視約束的描述,以一種迥異於建立 NSLayoutConstraints
物件的方式描述了我們對檢視的約束,我們以其中的一句作為例子來看看 Masonry
是如何實現鏈式 DSL
的。
make.top.equalTo(superview.mas_top).with.offset(padding.top);
複製程式碼
make.top
make
是 MASConstraintMaker
型別的物件,這個型別封裝了一系列只讀 MASConstraint
屬性,top
就是其中之一,宣告和實現如下:
@property (nonatomic, strong, readonly) MASConstraint *top;
複製程式碼
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
複製程式碼
addConstraintWithLayoutAttribute:
方法的實現為:
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
間接呼叫了 `constraint:addConstraintWithLayoutAttribute:` 方法:
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
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;
}
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
複製程式碼
讓我們一句一句來看:
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
複製程式碼
首先,會初始化一個 MASViewAttribute
型別的物件(viewAttribute
),該型別的初始化方法是:
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
複製程式碼
實現為:
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
return self;
}
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [super init];
if (!self) return nil;
_view = view;
_item = item;
_layoutAttribute = layoutAttribute;
return self;
}
複製程式碼
MASViewAttribute
是一個模型類,用於儲存檢視和檢視對應的 NSLayoutAttribute
。
接下來會初始化一個 MASViewConstraint
型別的物件(newConstraint
):
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
複製程式碼
MASViewConstraint
的初始化方法是:
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute;
複製程式碼
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
self = [super init];
if (!self) return nil;
_firstViewAttribute = firstViewAttribute;
self.layoutPriority = MASLayoutPriorityRequired;
self.layoutMultiplier = 1;
return self;
}
複製程式碼
MASViewConstraint
也是一個模型類,會通過剛才初始化的 viewAttribute
作為初始化引數,並儲存在 _firstViewAttribute
的例項變數中。
接下來,由於 constraint
引數為 nil, 所以直接走到這裡:
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
複製程式碼
將 newConstraint
物件的代理設為 self
(make
),同時將其放置到 constraints
陣列中。
簡而言之就是,make
中的 top
方法會初始化一個 MASViewAttribute
型別的物件 viewAttribute
,並通過該物件初始化一個 MASViewConstraint
型別的物件 newConstraint
,讓 make
作為 newConstraint
物件的 delegate
,並儲存在 make
的 constraints
屬性中。接下來,return newConstraint;
return newConstraint;
看似簡簡單單的一句程式碼,卻是 Masonry
這套鏈式 DSL
能夠生效的核心。
.equalTo
newConstraint
是 MASViewConstraint
型別的物件,而 MASViewConstraint
又是 MASConstraint
的子類。在 MASConstraint
中,宣告瞭一系列的方法,例如:
/**
* Sets the constraint relation to NSLayoutRelationEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))equalTo;
複製程式碼
這個方法的返回值是一個接受 id
型別的 attr
引數,返回 MASConstraint
型別的 block
,這樣寫的意義是,既可以做到傳遞引數,返回 self
,同時又確保了可以實現鏈式呼叫的 DSL
。
該方法的實現為:
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
複製程式碼
在方法的實現中,呼叫了一個名為 equalToWithRelation
的內部方法,方法的實現為:
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
複製程式碼
MASConstraint
其實是一個基類,其中 equalToWithRelation
方法本身的實現裡只有一個名為 MASMethodNotImplemented();
的巨集,這個巨集的實現僅僅是丟擲一個異常:
#define MASMethodNotImplemented() \
@throw [NSException exceptionWithName:NSInternalInconsistencyException \
reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
userInfo:nil]
複製程式碼
而我們在 makeConstraints
的時候,實際呼叫的是 MASViewConstraint
這個 MASConstraint
子類中的實現:
- (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;
}
};
}
複製程式碼
該方法接收兩個引數,一個表示了對應的屬性(mas_top
),一個表示了相等關係(NSLayoutRelationEqual
),進入方法後會先對我們傳入的屬性做一個型別判斷,我們傳入的是一個單個的屬性,所以會落入 else
分支,同樣是依賴斷言做了一系列保護性的判斷,並將相等關係和檢視屬性分別賦值給 layoutRelation
和 secondViewAttribute
屬性,並返回 self
。
返回 self
,看似簡簡單單的一個操作,卻是 Masonry
能夠實現鏈式 DSL 最重要的基石。(重要的事情說 n 遍)
superview.mas_top
再來看看我們傳入的 mas_top
,這是一個宣告在 View+MASAdditions.h
當中的只讀屬性:
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
複製程式碼
簡單生成並返回了一個 MASViewAttribute
屬性的物件:
- (MASViewAttribute *)mas_top {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTop];
}
複製程式碼
實現為:
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
複製程式碼
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
return self;
}
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [super init];
if (!self) return nil;
_view = view;
_item = item;
_layoutAttribute = layoutAttribute;
return self;
}
複製程式碼
.with
我們繼續往下看的時候,.with
同樣是實現在 MASViewConstraint
中的一個方法:
- (MASConstraint *)with {
return self;
}
複製程式碼
同樣是簡簡單單的返回了 self
,而且是僅僅做了這一件事情。所以這個方法僅僅是一個語法……好吧都不能叫做語法糖,就叫語氣助詞吧,是為了讓我們寫出的 DSL
可讀性更高而存在的。當然了,你要是覺得多餘,也是可以不寫的。
.offset
接下來是 offset
:
- (MASConstraint * (^)(CGFloat offset))offset;
複製程式碼
就是簡簡單單的一個賦值操作罷了,寫成這麼複雜的原因就是實現可以傳遞引數的鏈式呼叫。
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}
複製程式碼
以上就是 make.top.equalTo(superview.mas_top).with.offset(padding.top);
在 Masonry
內部到底做了些什麼,其餘幾句也是類似的,總而言之就是:
對
MASConstraintMaker
型別的物件屬性(MASConstraint
的子類)top
(或其他任何你想要去佈局的屬性),進行了初始化,並通過返回MASViewConstraint
型別的物件(newConstraint
),不斷地呼叫newConstraint
的物件方法,對newConstraint
中的屬性做了賦值,以確保其可以完整的表達一個NSLayoutConstraints
。
install
在配置好我們想要的約束後,我們還需要對檢視施加約束:
return [constraintMaker install];
複製程式碼
先來看一下 install
方法:
- (NSArray *)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;
}
複製程式碼
首先,會對 constraints
屬性做一份 copy
,之後遍歷 constraints
中的所有 MASConstraint
及其子型別的屬性,並呼叫其 install
方法:
- (void)install;
複製程式碼
實現為:
- (void)install {
if (self.hasBeenInstalled) {
return;
}
if ([self supportsActiveProperty] && self.layoutConstraint) {
self.layoutConstraint.active = YES;
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
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;
}
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
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;
}
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];
}
}
複製程式碼
接下來,我們來一段一段分析:
hasBeenInstalled
首先,在 install
之前,會做一次判斷,看是否已經被 install
過:
if (self.hasBeenInstalled) {
return;
}
複製程式碼
判斷依據則是:
- (BOOL)hasBeenInstalled {
return (self.layoutConstraint != nil) && [self isActive];
}
複製程式碼
layoutConstraint
是一個 MASLayoutConstraint
型別的 weak
屬性,MASLayoutConstraint
是 NSLayoutConstraint
的子類,只是為了增加一個屬性(mas_key
)。
@property (nonatomic, weak) MASLayoutConstraint *layoutConstraint;
複製程式碼
isActive
則是通過判斷 layoutConstraint
是否響應 isActive
以及 isActive
方法返回的結果,來綜合決定。
- (BOOL)isActive {
BOOL active = YES;
if ([self supportsActiveProperty]) {
active = [self.layoutConstraint isActive];
}
return active;
}
複製程式碼
- (BOOL)supportsActiveProperty {
return [self.layoutConstraint respondsToSelector:@selector(isActive)];
}
複製程式碼
mas_installedConstraints
if ([self supportsActiveProperty] && self.layoutConstraint) {
self.layoutConstraint.active = YES;
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
複製程式碼
如果 supportsActiveProperty
且 layoutConstraint
不為空,則將 layoutConstraint.active
設為 YES
,並將其新增到 firstViewAttribute.view
的 mas_installedConstraints
只讀屬性中去。
@property (nonatomic, readonly) NSMutableSet *mas_installedConstraints;
複製程式碼
mas_installedConstraints
是一個可變集合,是通過分類給 MAS_View 類新增的關聯物件,用來儲存已經 active
的物件。
static char kInstalledConstraintsKey;
- (NSMutableSet *)mas_installedConstraints {
NSMutableSet *constraints = objc_getAssociatedObject(self, &kInstalledConstraintsKey);
if (!constraints) {
constraints = [NSMutableSet set];
objc_setAssociatedObject(self, &kInstalledConstraintsKey, constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return constraints;
}
複製程式碼
生成約束
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;
}
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
複製程式碼
其實就是把我們再 block
中通過諸如 make.top.equalTo(superview.mas_top).with.offset(padding.top);
這樣的語句配置的屬性作為 MASLayoutConstraint
初始化方法的引數,生成一個約束。唯一需要注意的是,如果你設定的是一個 isSizeAttribute
,並且 secondViewAttribute
為 nil
,會做一些額外的引數調整。
施加約束
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;
}
複製程式碼
如果 secondViewAttribute.view
中的存在,就通過 mas_closestCommonSuperview
方法尋找最近的公共子檢視:
/**
* Finds the closest common superview between this view and another view
*
* @param view other view
*
* @return returns nil if common superview could not be found
*/
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
複製程式碼
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
MAS_VIEW *closestCommonSuperview = nil;
MAS_VIEW *secondViewSuperview = view;
while (!closestCommonSuperview && secondViewSuperview) {
MAS_VIEW *firstViewSuperview = self;
while (!closestCommonSuperview && firstViewSuperview) {
if (secondViewSuperview == firstViewSuperview) {
closestCommonSuperview = secondViewSuperview;
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
return closestCommonSuperview;
}
複製程式碼
遞迴求解。
如果設定的是一個尺寸約束(firstViewAttribute.isSizeAttribute
),則施加在 firstViewAttribute.view
上。否則施加在 firstViewAttribute.view.superView
上。
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];
}
複製程式碼
對檢視施加約束,並將 layoutConstraint
儲存在 self.layoutConstraint
屬性中,同時把 self
儲存到之前提到過的叫做 mas_installedConstraints
的關聯物件中。至此,文章開始提到的例子業已完成。
原文地址:Masonry 原始碼解讀(上)
如果覺得我寫的還不錯,請關注我的微博@小橘爺,最新文章即時推送~