Masonry源碼閱讀
AutoLayout是Apple在iOS6中新增的UI布局適配的方法,用來替代iOS6之前的AutoResizing。自動(dòng)布局的約束可以通過拖拽的方式添加,也可以通過代碼添加。AutoLayout對(duì)應(yīng)的代碼約束就是NSLayoutConstraint。
NSLayoutConstraint的API雖然很簡(jiǎn)單,但是添加約束的代碼量卻很大,因此出現(xiàn)了許多對(duì)NSLayoutConstraint的封裝,Masonry就是其中一個(gè)。
為了使子視圖填滿父視圖并且留10像素的間隙,來看看用官方API和Masonry添加約束的差別:
- 官方API:
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
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:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
- Masonry
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
可以明顯看到使用Masonry添加約束的代碼精簡(jiǎn)許多。
基本使用
Msonry的使用方法很簡(jiǎn)單,主要有以下三個(gè)方法:
-
mas_remakeConstraints會(huì)清除View已有的約束,再添加新的約束 -
mas_makeConstraints不會(huì)清楚之前的約束,直接添加新的約束 -
mas_updateConstraints如果該條件的約束已經(jīng)存在,則直接更新,否則添加新的約束。
添加約束的時(shí)機(jī)
查看官方實(shí)例即可一目了然
@implementation DIYCustomView
- (id)init {
self = [super init];
if (!self) return nil;
// --- Create your views here ---
self.button = [[UIButton alloc] init];
return self;
}
// tell UIKit that you are using AutoLayout
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
// --- remake/update constraints here
[self.button remakeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@(self.buttonSize.width));
make.height.equalTo(@(self.buttonSize.height));
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
- (void)didTapButton:(UIButton *)button {
// --- Do your changes ie change variables that affect your layout etc ---
self.buttonSize = CGSize(200, 200);
// tell constraints they need updating
[self setNeedsUpdateConstraints];
}
@end
有幾個(gè)需要注意的點(diǎn):
- updateConstraints方法中添加約束要用
mas_remakeConstraints方法 - updateConstraints方法中要在
最后調(diào)用父類的方法 - 更新約束后要調(diào)用
[self setNeedsUpdateConstraints]
setNeedsUpdateConstraints:當(dāng)一個(gè)自定義view的某個(gè)屬性發(fā)生改變,并且可能影響到constraint時(shí),需要調(diào)用此方法去標(biāo)記constraints需要在未來的某個(gè)點(diǎn)更新,系統(tǒng)然后調(diào)用updateConstraints;
updateConstraintsIfNeeded:立即觸發(fā)約束更新,自動(dòng)更新布局。
使用技巧
避免壓縮和拉伸
一般我們會(huì)通過以下代碼來添加兩個(gè)并列的視圖:
UILabel *typeLable = [[UILabel alloc] init];
typeLable.text = @"類型";
typeLable.textColor = [UIColor whiteColor];
typeLable.backgroundColor = [UIColor grayColor];
[self.view addSubview:typeLable];
[typeLable mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.view);
make.left.equalTo(self.view).offset(15);
}];
UILabel *titleLable = [[UILabel alloc] init];
titleLable.text = @"標(biāo)題";
titleLable.textColor = [UIColor blackColor];
titleLable.backgroundColor = [UIColor orangeColor];
titleLable.font = [UIFont systemFontOfSize:25];
[self.view addSubview:titleLable];
[titleLable mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(typeLable.mas_right);
make.centerY.equalTo(typeLable);
make.right.equalTo(self.view).offset(-15);
}];

運(yùn)行之后會(huì)發(fā)現(xiàn)左側(cè)的View被拉伸了,但實(shí)際上我們會(huì)希望左邊保持原有大小,而拉伸右側(cè)的。這時(shí)我們可以再添加另外的約束來實(shí)現(xiàn)這個(gè)效果:
[typeLable setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
[typeLable setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
[titleLable setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
[titleLable setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
最后的結(jié)果:

使某個(gè)約束失效
這種情況一般出現(xiàn)九宮格圖片時(shí),可能需要設(shè)置contentView的bottom:
make.bottom.equalTo(self.contentView);
如果cell被復(fù)用了,就需要使之前的底部約束失效,重新再設(shè)置。所有可以用一個(gè)全局變量保存這個(gè)MASConstraint,在重新設(shè)置之前先調(diào)用[constraint uninstall]。
解決常見的約束沖突警告
UITableViewCell 的約束沖突
2017-12-29 16:24:42.645364+0800 project[3804:770025] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
[
<MASLayoutConstraint:0x6040002b7d60 UILabel:0x7fa5e0c5c490.top == UITableViewCellContentView:0x7fa5e0c5c280.top + 10>,
<MASLayoutConstraint:0x6040002b85a0 UILabel:0x7fa5e0c5c770.top == UILabel:0x7fa5e0c5c490.bottom + 10>,
<MASLayoutConstraint:0x6040002b8f00 UILabel:0x7fa5e0c5d010.top == UILabel:0x7fa5e0c5c770.bottom + 5>,
<MASLayoutConstraint:0x6040002b94a0 UILabel:0x7fa5e0c5d5d0.top == UILabel:0x7fa5e0c5d010.bottom + 5>,
<MASLayoutConstraint:0x6040002b9800 UILabel:0x7fa5e0c5d8b0.top == UILabel:0x7fa5e0c5d5d0.bottom + 5>,
<MASLayoutConstraint:0x6040002b9aa0 UILabel:0x7fa5e0c5d8b0.bottom == UITableViewCellContentView:0x7fa5e0c5c280.bottom - 10>,
<NSLayoutConstraint:0x600000290810 UITableViewCellContentView:0x7fa5e0c5c280.height == 43.6667>
]
Will attempt to recover by breaking constraint
<MASLayoutConstraint:0x6040002b85a0 UILabel:0x7fa5e0c5c770.top == UILabel:0x7fa5e0c5c490.bottom + 10>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
找到被覆蓋的約束:<MASLayoutConstraint:0x6040002b85a0 UILabel:0x7fa5e0c5c770.top == UILabel:0x7fa5e0c5c490.bottom + 10>,給它添加優(yōu)先級(jí)即可消除警告:
make.top.equalTo(self.titleLable.mas_bottom).offset(10).priority(500);
自定義View的NSAutoresizingMaskLayoutConstraint沖突
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<MASLayoutConstraint:0x7fafcb1c8e10 UIButton:0x7fafcb18e160.width == UIView:0x7fafcb0bd380.width - 60>",
"<NSAutoresizingMaskLayoutConstraint:0x7fafcb0a2570 UIView:0x7fafcb0bd380.width == 0>"
)
Will attempt to recover by breaking constraint
<MASLayoutConstraint:0x7fafcb1c8e10 UIButton:0x7fafcb18e160.width == UIView:0x7fafcb0bd380.width - 60>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
解決方法:View的translatesAutoresizingMaskIntoConstraints設(shè)置為NO
將UIStackView的translatesAutoresizingMaskIntoConstraints設(shè)為NO
如果設(shè)置了UIStackView的Spacing可能會(huì)出現(xiàn)沖突,此時(shí)需要將UIStackView的translatesAutoresizingMaskIntoConstraints設(shè)為NO。
源碼解析
Masonry之所以可以如此簡(jiǎn)潔地添加約束,一個(gè)主要原因是它可以使用鏈?zhǔn)秸Z法。在后面的源碼閱讀中可以重點(diǎn)看看它是如何實(shí)現(xiàn)的。
添加一個(gè)簡(jiǎn)單的約束:
UIView *view = [[UIView alloc] init];
[self.view addSubview:view];
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view);
}];
跟蹤代碼會(huì)發(fā)現(xiàn)它進(jìn)入了分類的一個(gè)方法中:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
這里做了4件事:
- 1、將View的
translatesAutoresizingMaskIntoConstraints屬性設(shè)置為NO; - 2、初始化了一個(gè)
MASConstraintMaker; - 3、調(diào)用添加約束的block;
- 4、調(diào)用
[constraintMaker install];
這里View并沒有持有block,block執(zhí)行后就會(huì)釋放,所有不會(huì)形成循環(huán)引用。
來到我們寫的添加約束的代碼中make.left.equalTo(self.view);:
首先是make.left,來到MASConstraintMaker的edges方法,返回的是一個(gè)MASConstraint:
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
最終會(huì)來到:
- (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;
}
- 1、根據(jù)NSLayoutAtrribute生成MASViewAttribute;
- 2、根據(jù)MASViewAtrribute生成MASViewConstraint,這個(gè)viewAtrribute是newConstraint的firstViewAttribute;
- 3、如果傳來的constraint不為空且為MASViewConstraints,則生成一個(gè)MASCompositeConstraint,并替換原有的constraint;
- 4、否則將newConstraint的delegate設(shè)為此MASConstaintMaker, 并保存;
這里的delegate都是為了實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用而實(shí)現(xiàn)的,因?yàn)槲覀兛赡軙?huì)這樣調(diào)用make.left.right,make.left返回的是MASViewConstraint,它也有right方法:
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
最后調(diào)用的是delegate的方法,從前面可以知道delegate其實(shí)就是MASConstraintMaker,所以最后回到MASConstraintMaker來創(chuàng)建。
這里調(diào)用的[self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]傳入了self,也就是MASViewConstraint(make.left),最后會(huì)被新的MASCompositeConstraint替換。
到目前為止都是為了創(chuàng)建MASConstraint,主要出現(xiàn)了三個(gè)類,MASConstraintMaker、MASViewConstraint、MASCompositeConstraint。
MASConstraintMaker就是為了給View創(chuàng)建MASConstraint的。它有個(gè)屬性constraints用來保存每一個(gè)約束。
MASViewConstraint和MASCompositeConstraint都繼承自MASConstraint。
MASViewConstraint有兩個(gè)重要的屬性,firstViewAttribute和secondViewAttribute,都是MASViewAttribute,而MASViewAttribute保存的又是NSLayoutAttribute。來看原生的創(chuàng)建約束的方法:
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top]
可以看到有兩個(gè)attribute,MASViewConstraint的這兩個(gè)MASViewAtrribute也就是要用在這里的。
MASCompositeConstraint可以看做一組MASViewConstraint的集合。它有個(gè)childConstraints用來保存它的子約束。在調(diào)用make.edges時(shí)返回的就是MASCompositeConstraint:
- (MASConstraint *)edges {
return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}
- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs {
__unused MASAttribute anyAttribute = (MASAttributeLeft | MASAttributeRight | MASAttributeTop | MASAttributeBottom | MASAttributeLeading
| MASAttributeTrailing | MASAttributeWidth | MASAttributeHeight | MASAttributeCenterX
| MASAttributeCenterY | MASAttributeBaseline
| MASAttributeFirstBaseline | MASAttributeLastBaseline
#if TARGET_OS_IPHONE || TARGET_OS_TV
| MASAttributeLeftMargin | MASAttributeRightMargin | MASAttributeTopMargin | MASAttributeBottomMargin
| MASAttributeLeadingMargin | MASAttributeTrailingMargin | MASAttributeCenterXWithinMargins
| MASAttributeCenterYWithinMargins
#endif
);
NSAssert((attrs & anyAttribute) != 0, @"You didn't pass any attribute to make.attributes(...)");
NSMutableArray *attributes = [NSMutableArray array];
if (attrs & MASAttributeLeft) [attributes addObject:self.view.mas_left];
if (attrs & MASAttributeRight) [attributes addObject:self.view.mas_right];
if (attrs & MASAttributeTop) [attributes addObject:self.view.mas_top];
if (attrs & MASAttributeBottom) [attributes addObject:self.view.mas_bottom];
if (attrs & MASAttributeLeading) [attributes addObject:self.view.mas_leading];
if (attrs & MASAttributeTrailing) [attributes addObject:self.view.mas_trailing];
if (attrs & MASAttributeWidth) [attributes addObject:self.view.mas_width];
if (attrs & MASAttributeHeight) [attributes addObject:self.view.mas_height];
if (attrs & MASAttributeCenterX) [attributes addObject:self.view.mas_centerX];
if (attrs & MASAttributeCenterY) [attributes addObject:self.view.mas_centerY];
if (attrs & MASAttributeBaseline) [attributes addObject:self.view.mas_baseline];
if (attrs & MASAttributeFirstBaseline) [attributes addObject:self.view.mas_firstBaseline];
if (attrs & MASAttributeLastBaseline) [attributes addObject:self.view.mas_lastBaseline];
#if TARGET_OS_IPHONE || TARGET_OS_TV
if (attrs & MASAttributeLeftMargin) [attributes addObject:self.view.mas_leftMargin];
if (attrs & MASAttributeRightMargin) [attributes addObject:self.view.mas_rightMargin];
if (attrs & MASAttributeTopMargin) [attributes addObject:self.view.mas_topMargin];
if (attrs & MASAttributeBottomMargin) [attributes addObject:self.view.mas_bottomMargin];
if (attrs & MASAttributeLeadingMargin) [attributes addObject:self.view.mas_leadingMargin];
if (attrs & MASAttributeTrailingMargin) [attributes addObject:self.view.mas_trailingMargin];
if (attrs & MASAttributeCenterXWithinMargins) [attributes addObject:self.view.mas_centerXWithinMargins];
if (attrs & MASAttributeCenterYWithinMargins) [attributes addObject:self.view.mas_centerYWithinMargins];
#endif
NSMutableArray *children = [NSMutableArray arrayWithCapacity:attributes.count];
for (MASViewAttribute *a in attributes) {
[children addObject:[[MASViewConstraint alloc] initWithFirstViewAttribute:a]];
}
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
constraint.delegate = self;
[self.constraints addObject:constraint];
return constraint;
}
這里主要做了以下5件事:
- 1、將
MASAtrribute轉(zhuǎn)為MASViewAtrribute數(shù)組,比如把make.edges轉(zhuǎn)為@[self.view.mas_left, self.view.mas_right, self.view.mas_top, self.view.mas_bottom]; - 2、再把
MASViewAtrribute數(shù)組轉(zhuǎn)為MASViewConstraint數(shù)組; - 3、創(chuàng)建最終的MASCompositeConstraint,
MASViewConstraint數(shù)組就是它的childConstraints; - 4、保存上面創(chuàng)建的MASCompositeConstraint到MASConstraintMaker的constraints中,以備后面install。
到目前為止拿到的都是firstViewAtrribute,接下來的.equalTo等則是拿到secondViewAtrribute。
.equalTo(self.view)其實(shí)是鏈?zhǔn)秸Z法,調(diào)用.equalTo得到一個(gè)block,然后調(diào)用這個(gè)block,相當(dāng)于:
id(^block)(id attribute) = constraint.mas_equalTo;
if (block) {
block(self.view);
}
之所以要返回block,是因?yàn)樗獋鬟f參數(shù)。源碼:
- (MASConstraint *(^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
來到MASViewConstraint的equalToWithRelation(attribute, NSLayoutRelationEqual),它同樣是鏈?zhǔn)秸Z法,這里的atrribute就是make.left.equalTo(self.view)中的self.view.mas_left。
- (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;
}
};
}
因?yàn)檫@里我們的調(diào)用是make.left.equalTo(self.view),所以atrribute是View,直接走到self.secondViewAttribute = attribute:
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
MASViewAttribute *attr = secondViewAttribute;
if (attr.layoutAttribute == NSLayoutAttributeNotAnAttribute) {
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:attr.view item:attr.item layoutAttribute:self.firstViewAttribute.layoutAttribute];;
} else {
_secondViewAttribute = secondViewAttribute;
}
} else {
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
}
因?yàn)槭荲iew,他會(huì)根據(jù)firstViewAtrribute來創(chuàng)建secondViewAtrribute
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
到這secondViewAtrribute也拿到了。
設(shè)置偏差值:make.left.equalTo(self.view).offset(5):
- (MASConstraint *(^)(NSValue *value))valueOffset {
return ^id(NSValue *offset) {
NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
[self setLayoutConstantWithValue:offset];
return self;
};
}
- (void)setLayoutConstantWithValue:(NSValue *)value {
if ([value isKindOfClass:NSNumber.class]) {
self.offset = [(NSNumber *) value doubleValue];
} else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
CGPoint point;
[value getValue:&point];
self.centerOffset = point;
} else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
CGSize size;
[value getValue:&size];
self.sizeOffset = size;
} else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets insets;
[value getValue:&insets];
self.insets = insets;
} else {
NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
}
}
添加約束完成后,開始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;
}
關(guān)鍵:[constraint install]
這段代碼很長(zhǎng),主要完成的工作:
- 1、通過firstViewAtrribute和secondViewAtrribute分別拿到對(duì)應(yīng)的View和NSLayoutAttribute;
- 2、如果是類似make.left.equalTo(@10)這樣添加的約束,則secondViewAtrribute是空,默認(rèn)會(huì)把firstViewAtrribute的view的superView和NSLayoutAttribute作為第二個(gè)約束;
- 3、生成MASLayoutConstraint,其實(shí)就是繼承自NSLayoutConstraint,多了一個(gè)參數(shù)mas_key,用來調(diào)試。當(dāng)約束沖突時(shí),可以設(shè)置View的mas_key來確定是哪個(gè)View發(fā)生了沖突;
- 4、設(shè)置installedView,也就是用來addConstraint的View:如果secondViewAtrribute的View不為空,則installedView為firstView和secondView最相近的superView;否則如果firstViewAtrribute是sizeAtrribute,那installedView就是firstViewAtrribute的View本身;否則installView就是firstViewAtrribute的View的superView;
- 5、調(diào)用原生API addConstraint,添加約束,并在View的mas_installedConstraints保存約束。
至此,Constraint設(shè)置完成。
找到最近的公共superView的代碼:
- (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;
}
mas_key在View的分類中也添加了:
@interface MAS_VIEW (MASAdditions)
@property (nonatomic, strong) id mas_key;
@end
實(shí)現(xiàn)的方法:
- (id)mas_key {
return objc_getAssociatedObject(self, @selector(mas_key));
}
- (void)setMas_key:(id)key {
objc_setAssociatedObject(self, @selector(mas_key), key, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
Masonry提供了一個(gè)宏來批量設(shè)置View的mas_key:
#define MASAttachKeys(...) \
{ \
NSDictionary *keyPairs = NSDictionaryOfVariableBindings(__VA_ARGS__); \
for (id key in keyPairs.allKeys) { \
id obj = keyPairs[key]; \
NSAssert([obj respondsToSelector:@selector(setMas_key:)], \
@"Cannot attach mas_key to %@", obj); \
[obj setMas_key:key]; \
} \
}
參考