Masonry源碼閱讀配合下面兩篇文章足矣。第一篇比較簡單,主講大框架。第二篇比較詳細(xì),細(xì)節(jié)點(diǎn)較多。那我呢?我來講講進(jìn)階吧。講一些
Draveness blog
from cocoachina
先看看原生的布局是怎么做的。
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIView *view2 = [[UIView alloc] init];
view2.translatesAutoresizingMaskIntoConstraints = NO;
view2.backgroundColor = [UIColor blueColor];
[superview addSubview:view2];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:view2
attribute:NSLayoutAttributeLeft
multiplier:1
constant:-padding.right],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:view2
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0],
//view2 constraints
[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:view1
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
Masonry做的事情就用點(diǎn)語法方便的把整個(gè)過程封裝了起來。比如
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top]
等價(jià)于 Masonry 的。當(dāng)然此時(shí)是view1調(diào)用了makeConstraints函數(shù)
make.top.mas_equalTo(superview.top).offset(padding.top)multipliedBy(1);
讀 masonry源碼還需要有點(diǎn)語法+block 的基礎(chǔ),讀者自行補(bǔ)充。導(dǎo)讀開始!show time~
Tip1:Autoresizing
self.translatesAutoresizingMaskIntoConstraints = NO;
self.translatesAutoresizingMaskIntoConstraints = NO;
關(guān)閉Autoresizing。 不懂的可以看看這個(gè)。如果是 YES,autolayout將無效。
Autoresizing相關(guān) blog
Tip2:make.left.right.top.bottom發(fā)生了什么
make.left.right.top.bottom.mas_equalTo(superview)
到底發(fā)生了什么?一步一步推導(dǎo)!
make 是 MASConstraintMaker
make.left 是MASConstraintMaker的實(shí)例對象調(diào)用了 left 方法,make.left返回了newConstraint。記住newConstraint的類型是MASViewAttribute,很重要!
- (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]) {
...此處不是MASViewConstraint,所以忽略
}
if (!constraint) {
newConstraint.delegate = self;//設(shè)置了代理!
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
make.left.right ,make.left返回的是MASViewAttribute,所以這時(shí)候去MASViewAttribute的對象方法里面找 right。它的父類MASConstraint實(shí)現(xiàn)了 right 方法
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
然后
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
此時(shí)有一個(gè)代理,注意之前的代碼! newConstraint.delegate = self
,代理是 make!所以有跑到了這里!
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
//這個(gè)時(shí)候constraint就是 make.left 產(chǎn)生的MASViewConstraint?。?!
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];//里面就剩一個(gè)約束了。
return compositeConstraint;
}
//下面都不走了!上面已經(jīng)返回了!
if (!constraint) {
//此處不走!
}
return newConstraint;
}
so make.left.right返回了MASCompositeConstraint。里面有兩個(gè)MASViewConstraint。MASCompositeConstraint里有childConstraints,里面存放著一個(gè)又一個(gè)的MASViewConstraint。
make.left.right.top ---
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
return self;
}
|
V
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:
id<MASConstraintDelegate> strongDelegate = self.delegate;
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
newConstraint.delegate = self;
[self.childConstraints addObject:newConstraint];
return newConstraint;
}
這里調(diào)用strongDelegate去做 add 約束的動(dòng)作。compositeConstraint.delegate = self;
strongDelegate就是 make,(make 內(nèi)心是崩潰的,怎么又是我?。?/p>
- (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]) {
//此時(shí)是MASCompositeConstraint,所以也不發(fā)生!
}
if (!constraint) {
//..不是 nil,不發(fā)生。
}
return newConstraint;
}
所以make.left.right.top其實(shí)就[self.childConstraints addObject:newConstraint];
添加了一個(gè)新的約束。此時(shí)要注意一個(gè)細(xì)節(jié),return newConstraint;
這個(gè)細(xì)節(jié)坑了我 N 久。這里返回了newConstraint,所以make.left.right.top返回的是MASViewConstraint?不是。這里返回了MASViewConstraint,但是沒有去接收這個(gè)約束。MASCompositeConstraint返回的是 self。太狡詐了~~~
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
return self;
}
so make.left.right.top返回MASCompositeConstraint,且添加了一個(gè)約束。
make.left.right.top.bottom 這里和上面一樣。
這里再次總結(jié)下!
make.left->MASViewConstraint
make.left.right-> MASCompositeConstraint
make.left.right.top-> MASCompositeConstraint
make.left.right.top.bottom-> MASCompositeConstraint
addConstraint這個(gè)動(dòng)作都會(huì)在 make 中發(fā)生。
最后 make.left.right.top.bottom.mas_equalTo(superview)
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
遍歷約束,相對于調(diào)用MASViewConstraint的equalTo方法。
Tip3:masory 巧妙架構(gòu)
這個(gè)是對tip2的補(bǔ)充。
??MASConstraint
是MASViewConstraint
和MASCompositeConstraint
的父類。
??MASCompositeConstraint
的重點(diǎn)是有一個(gè)存放MASViewConstraint
的childConstraints
。由于繼承了MASConstraint
所以又可以調(diào)用MASConstraint
的所有方法。使鏈?zhǔn)秸Z法可以繼續(xù)~
??這里有一個(gè)思想!
??父類,子類,子類組。
??子類組用起來和子類沒區(qū)別,但實(shí)際發(fā)生鏈?zhǔn)秸Z法之后,每次都把新生成的子類收集到了自己里面,讓自己變大。
??make充當(dāng)了一個(gè)啟動(dòng)器,產(chǎn)生了第一個(gè)MASViewConstraint,使后面鏈?zhǔn)娇梢耘芷饋恚?make 也充當(dāng)了一個(gè)生成MASConstraint
生成器的角色,所有的MASConstraint
都來自make。這簡直太妙了!我水平有限,不知道怎么恰當(dāng)形容。
Tip4:mas_closestCommonSuperview
尋找共同的父控件到底發(fā)生了什么?下面的代碼讓我一度很困惑。我不能理解!closestCommonSuperview && firstViewSuperview
怎么可能會(huì)為0,后來我意識到firstViewSuperview.superview的父控件是有限的。它最后可能會(huì)為 nil。
- (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;
}
他不能直接寫成這樣?
if self.superview == view.superview
return self.superview
else
return nil
然后我測試了下,兩個(gè)視圖的父控件不是一個(gè),比如View1的爺控件等于 View2 的父控件,布局也是可以進(jìn)行的。好吧,我 too naive。確實(shí)應(yīng)該寫成尋找共同最小父控件。
Tip5:NSLayoutAttributeLeftMargin是什么
iOS 8新增屬性。下面兩句話等價(jià)!
make.leftMargin.equalTo(10);
make.left.equalTo(another.left).offset(10);
這么用在父控件上當(dāng)然可以!但是!
make.leftMargin.equalTo(10);
make.left.equalTo(superview.left).offset(10);
注意
如果superview是控制器的 self.view。那布局會(huì)出問題。會(huì)有一定的誤差。這是系統(tǒng)問題。可以看看官方文檔。
Tip6:優(yōu)先級
MASLayoutPriorityRequired = UILayoutPriorityRequired;
MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
MASLayoutPriorityDefaultMedium = 500;
MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint. Do not exceed this.
UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50;
每一條約束默認(rèn)都是必須的,必須的意思是1000。我常用的就是這個(gè)。
//效果一樣。
make.width.priority(749);
make.width.equalTo(@(10)).priorityLow();
//如果有兩條約束,控件的高為60.
//假如你在外部調(diào)用[globalconstraint deactivate],此時(shí)高度就變成了30.
//其實(shí)這么用起來和 update 差不多。
make.height.equalTo(@30).priorityLow();
globalconstraint = make.height.equalTo(@60);
Tip7:group
我在網(wǎng)上找了很多 group 的用法,愣是沒找著。我簡單測試了下。其實(shí) group 的用處就是可以返回MASCompositeConstraint。有什么用就靠你的想象力了!
make.width.height.equalTo(redView);
make.bottom.equalTo(blueView.top).offset(-padding);
make.group(^(){
make.top.greaterThanOrEqualTo(superview.top).offset(padding);
make.left.equalTo(superview.left).offset(padding);
make.right.equalTo(redView.left).offset(-padding);
});
make.height.equalTo(blueView.height);
make.width.height.equalTo(redView);
make.bottom.equalTo(blueView.top).offset(-padding);
make.top.greaterThanOrEqualTo(superview.top).offset(padding);
make.left.equalTo(superview.left).offset(padding);
make.right.equalTo(redView.left).offset(-padding);
make.height.equalTo(blueView.height);
最后附上本人 github 源碼備注。歡迎交流技術(shù)!
Masonry源碼備注