想必在AFNetworking之後,Masonry成了廣大iOS開發者日常開發不可或缺的三方庫之一。它的使用真的非常簡單,例如:
[greenView makeConstraints:^(MASConstraintMaker *make) {
make.top.greaterThanOrEqualTo(superview.top).offset(padding);
make.left.equalTo(superview.left).offset(padding);
make.bottom.equalTo(blueView.top).offset(-padding);
make.right.equalTo(redView.left).offset(-padding);
make.width.equalTo(redView.width);
make.height.equalTo(redView.height);
make.height.equalTo(blueView.height);
}];
複製程式碼
從這段短小精悍的程式碼中,我們已經能夠挖掘出它背後的原理。本文假設你對Masonry已經有基本的瞭解和使用。
首先我們提取一些“關鍵詞”,他們究竟是啥意思?
1.makeConstraints:
2.make
3.left、right、height...
4.equalTo()
1.makeConstraints:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
複製程式碼
-
MASConstraintMaker
被傳入了block()中,即MASConstraintMaker
例項就是我們的第二個“關鍵詞”---make
。從[constraintMaker install]
還可看出,make負責了約束的新增。後文詳細介紹make
。 -
translatesAutoresizingMaskIntoConstraints
,官方解釋大致為:預設情況下它是YES
,即view
的autoresizing mask
會自動成為它的佈局。如果我們希望手動佈局,需要將它設為NO
2.make(MASConstraintMaker)
從鏈式呼叫make.top.equalTo...
,我們可以看出,make
中定義了這些約束屬性,它們是這樣實現的:
// step 1
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
// step 2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
// step 3
- (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
從實現檔案中得知,MASViewAttribute = View + NSLayoutAttribute + item
,這是一個可變元組,儲存了View
和與它相關的約束資訊。 -
MASViewConstraint
這就是一個約束,它包含firstViewAttribute
和secondViewAttribute
。 -
由step2可知,單純的約束屬性在該方法下的第一個引數都是
nil
,所以我們先直接看這種情況下的step3的執行情況。它被加入了一個約束陣列中。
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
複製程式碼
4)鏈式呼叫的實現
我們知道Masonry
鏈式呼叫是它廣受好評的優點之一。那麼,是如何做到make.top.left
這樣的操作呢?
由step3得知,make.top
的返回型別是MASViewConstraint
,那MASViewConstraint
中是如何呼叫到left的呢?
從MASViewConstraint
的父類MASConstraint
可以看到,這裡也定義了所有的佈局屬性,而這些佈局屬性的實現方式,如下,例:
// MASConstraint.m
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
// MASViewConstraint.m
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
複製程式碼
MASViewConstraint
把這個方法的具體實現委託給了代理方法。而在step3中,這個代理正是MASConstraintMaker
。不同的是,此時,step2方法的引數不在是nil
了,而是當前約束屬性。這也是的step3的處理邏輯不一樣了。(回到step3中再看看!)此時,MASCompositeConstraint
登場了,它是一個約束組合。以make.top.left...
為例,這個約束組合包含了top
和left
,並且呼叫了shouldBeReplacedWithConstraint:
方法,如下:
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.constraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
複製程式碼
以make.top.left...
為例,這個方法找到了之前儲存top
約束的位置,並替換成了約束組合。最終,top
和left
就一起被加入和make
的約束陣列中。
3.install
從上文看,我們已經拿到了所有的約束。這些約束是如何加入到檢視上的?來看看install
方法。
摘錄一下MASConstraintMaker
中的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;
}
複製程式碼
make
的處理相對簡單,再看看MASViewConstraint
本身是如何install
的。由於方法太長,我不貼上完整的程式碼了(分開來會比較容易看懂)。大致的流程如下:
1.如何約束是設定width
和height
,這是檢視自身的屬性,則把當前檢視的父檢視作為關聯檢視(程式碼中為secondLayoutItem
)。即這兩個約束是相對於父檢視設定的。
2.如果佈局上存在相對檢視,即通常寫法中的equalTo(someView.mas_top)
這樣,則找到這2個檢視最近的公共父檢視,並把約束新增在這個父檢視上。程式碼如下:
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;
}
複製程式碼
3.最後,如果是更新約束操作,則找出需要更新的約束單獨修改;否則,為檢視新增約束,並記錄在mas_installedConstraints
中。
到這裡,約束的新增已經完全梳理明白了。
4.equalTo()
還是得看具體實現方法:
- (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;
}
};
}
複製程式碼
這裡就比較好理解了,如果傳入的是約束陣列,則把他包裝成MASCompositeConstraint
,以同樣的方式加入到make
的constraints
中。如果是單個約束,則將其設為secondViewAttribute
,用於install
的時候使用。
總結
1.MASConstraintMaker
作為工廠,生產一個個MASViewConstraint
約束物件。
2.MASViewConstraint
和MASCompositeConstraint
繼承於抽象類MASConstraint
,為我們提供了高度封裝的約束物件
3.View+MASAdditions
這個UIView
的擴充套件是Masonry
與外界互動的介面類,這樣很好的把複雜的約束邏輯封裝在內部管理,又提供了簡單的API供使用者使用。
你看懂了嘛~