作者: 溫桂龍
部門: 新業(yè)務(wù)支持研發(fā)團(tuán)隊(duì)
0、iOS布局的發(fā)展史
在iOS發(fā)展早期,由于iPhone屏幕大小是固定的,開發(fā)者不需要考慮因屏幕大小差異而造成的適配的問題,在開發(fā)應(yīng)用進(jìn)行布局時(shí),采用的是直接通過代碼計(jì)算控件在其父控件的位置和大小的方式對UI控件進(jìn)行布局。
再到后面,在iPad推出后,apple公司推出autoresizing用于指定當(dāng)UI控件的父控件發(fā)生變化時(shí)如何調(diào)整布局,達(dá)到屏幕適配的效果。
到了iOS6推出的時(shí)候,iPhone以及iPad的屏幕尺寸逐漸變多,為了能更好地對不同的屏幕進(jìn)行適配,apple公司推出了基于約束的、描述性的autoLayout布局系統(tǒng)對不同屏幕的iOS設(shè)備進(jìn)行屏幕適配。
在iOS8中,apple公司推出了sizeClass布局系統(tǒng)用以支持更多屏幕大小不一樣的iOS設(shè)備的屏幕適配。
1、iOS布局方式的比較
代碼計(jì)算frame
無論怎樣的布局方式,最基本的原則都是指定控件的位置與大小。如果屏幕的大小是固定不變的,則只有橫屏和豎屏兩種情況,通過代碼計(jì)算出控件的位置即可。frame是指控件在其父控件中的位置和大小,在iOS中,可以使用具體的frame初始化view,view被創(chuàng)建后也可以對frame進(jìn)行修改。
frame示例:
//創(chuàng)建一個(gè)view,其相對父控件左邊距為10、上邊距為10;寬高均為10
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 10, 10)];
//修改view的frame,左、上邊距均改為15;寬高分別改為20和30
view.frame = CGRectMake(15, 15, 20, 30);
使用直接計(jì)算frame的方式,控件的位置與大小都是直接寫死的,基本上是沒有適配可言的。
autoresizing
使用autoresizing布局時(shí),可以指定view的autoresizingMask屬性,即當(dāng)UI控件的父控件發(fā)生變化時(shí)如何調(diào)整布局的屬性,共有六個(gè)枚舉值。
autoresizingMask屬性及autoresizing布局示例:
UIViewAutoresizingNone//默認(rèn)值,不自動調(diào)整布局
UIViewAutoresizingFlexibleLeftMargin //保持右邊距不變,調(diào)整左邊距
UIViewAutoresizingFlexibleWidth//保持左、右邊距不變,調(diào)整控件的寬度
UIViewAutoresizingFlexibleRightMargin//保持左邊距不變,調(diào)整右邊距,
UIViewAutoresizingFlexibleTopMargin//保持下邊距不變,調(diào)整上邊距
UIViewAutoresizingFlexibleHeight//保持上、下邊距不變,調(diào)整控件高度
UIViewAutoresizingFlexibleBottomMargin//保持上邊距不變,調(diào)整下邊距,
UIView *view = [[UIView alloc] init];
//父控件變化時(shí),保持寬度與高度不變,自動調(diào)整與父控件的左右邊距與上下邊距
view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
使用autoresizing進(jìn)行布局,控件調(diào)整布局依賴于父控件的布局變化,所以只適用于描述父子控件之間的調(diào)整關(guān)系,而不適用于描述同一層級之間的控或者沒有層級關(guān)系的控件之間的關(guān)系。使用autoresizing,對屏幕適配而言,依然有比較大的局限性。
autoLayout
autoLayout的主要概念是參照與約束。它關(guān)注的不是控件位置與大小的具體數(shù)值,而是關(guān)注控件屬性參照另一個(gè)控件的屬性的約束關(guān)系,該約束關(guān)系一般是線性關(guān)系。
autoLayout示例:
UIView *view = [[UIView alloc] init];
NSLayoutConstraint *lc =[NSLayoutConstraint constraintWithItem:anotherView//被約束的控件
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:view//參照控件
attribute:NSLayoutAttributeLeft
multiplier:2//線性關(guān)系倍數(shù)
constant:0];//乘以倍數(shù)后需要加的數(shù)值
[view addConstraint:lc];//添加約束到參照控件
添加約束的規(guī)則:添加到其最近的父控件。
- 兄弟控件添加到共同的父控件。
- 父子控件添加到父控件。
- 非父子控件添加到最近層級的父控件
autoLayout約束屬性:
NSLayoutAttributeLeft,
NSLayoutAttributeRight,
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,
NSLayoutAttributeTrailing,
NSLayoutAttributeWidth,
NSLayoutAttributeHeight,
NSLayoutAttributeCenterX,
NSLayoutAttributeCenterY,
NSLayoutAttributeLastBaseline,
NSLayoutAttributeBaseline NS_SWIFT_UNAVAILABLE("Use 'lastBaseline' instead") = NSLayoutAttributeLastBaseline,
NSLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeNotAnAttribute = 0
使用autoLayout進(jìn)行布局,不再關(guān)注控件的位置與大小的具體數(shù)值,而是可以通過描述控件的約束條件對控件進(jìn)行調(diào)整,可以設(shè)置約束的屬性相當(dāng)?shù)娜?,而且對于不同層級的控件,只需要將約束添加到其最近的父控件即可,這就使得使用autoLayout進(jìn)行布局,可以很方便地進(jìn)行屏幕適配工作。但是可以看出,使用純代碼布局的話,autoLayout添加一個(gè)約束的代碼量將會比較多。這也是使得基于autoLayout的第三方布局框架Masonry流行的原因之一。
sizeClass
在iOS8中推出的sizeClass,將屏幕的寬高抽象為三種 ,即所有的設(shè)備分為3*3共9種
- Compact:緊湊的
- Regular:正常的
- Any:任意的
通過指定屏幕的寬高類型,就不再需要根據(jù)屏幕的具體尺寸去進(jìn)行適配,甚至也不再有橫豎屏的概念了。但是sizeClass只是對屏幕的寬高進(jìn)行了分類,舍棄了具體尺寸的概念,具體的適配工作仍然需要autoLayout來實(shí)現(xiàn)。
小結(jié)
計(jì)算frame是最原始也是最直接的布局方式,它直接指定來控件的大小和位置,而這也是布局的根本所在,無論autoresizing或autoLayout最終也只是為了確定在不同大小的屏幕下控件的大小和位置。直接計(jì)算frame的方式或autoresizing,在iOS設(shè)備越來越多的現(xiàn)狀下顯然難以優(yōu)雅地解決屏幕適配問題。而無論是手寫代碼布局,或者是使用Xib、Stroyboard等可視化布局,autoLayout都是目前比較好選擇。但是autoLayout亦存在代碼重復(fù)且代碼量比較多的問題。而基于autoLayout的Masonry框架是對原生autoLayout的一種優(yōu)化,可以使autoLayout用起來變得相對簡潔優(yōu)雅。本文后面的內(nèi)容主要探討的也是Masonry框架的使用及其原理。
2、Masonry的基本使用
項(xiàng)目中使用Masonry:
在項(xiàng)目的podfile文件中添加Masonry,然后相應(yīng)地pod install即可。
target 'Demo' do
platform :ios, '8.0'
project 'Demo.xcodeproj'
pod 'Masonry'
end
Masonry布局的基本例子
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
self.redView = redView;
[self.view addSubview:redView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.mas_equalTo(0);//上下邊距均為0
make.centerX.mas_equalTo(self.view.mas_centerX);//X軸中心對齊self.view的X軸中心。
make.width.mas_equalTo(self.view.frame.size.width/3.0);//寬度為self.view的寬度1/3。
}];
UIView *greenView = [[UIView alloc] init];
greenView.backgroundColor = [UIColor greenColor];
self.greenView = greenView;
[self.view addSubview:greenView];
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.bottom.mas_equalTo(0);
make.right.mas_equalTo(redView.mas_left);
}];
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
self.blueView = blueView;
[self.view addSubview:blueView];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.top.bottom.mas_equalTo(0);
make.left.mas_equalTo(redView.mas_right);
}];
上面代碼定義一個(gè)redView,上下邊距為0,寬度為self.view.frame即頁面寬度的1/3,水平居中。redView左邊和右邊的其余空間分別為一個(gè)greenView和一個(gè)blueView,這時(shí)就可以以redView為基準(zhǔn)約束greenView的右邊與blueView的左邊。
make.right.mas_equalTo(redView.mas_left)//約束(greenView的)right對齊redView的left。
make.left.mas_equalTo(redView.mas_right)//約束(blueView的)left對齊redView的right。
同時(shí),Masonry支持鏈?zhǔn)秸{(diào)用,make.top.bottom.mas_equalTo(0)等同于
make.top.mas_equalTo(0)
make.bottom.mas_equalTo(0)

Masonry框架主要約束
mas_left
mas_top
mas_right
mas_bottom
mas_leading
mas_trailing
mas_width
mas_height
mas_centerX
mas_centerY
mas_baseline
- (MASViewAttribute *(^)(NSLayoutAttribute))mas_attribute
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
mas_firstBaseline
mas_lastBaseline
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
mas_leftMargin
mas_rightMargin
mas_topMargin
mas_bottomMargin
mas_leadingMargin
mas_trailingMargin
mas_centerXWithinMargins
mas_centerYWithinMargins
#endif
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)
mas_safeAreaLayoutGuide
mas_safeAreaLayoutGuideTop
mas_safeAreaLayoutGuideBottom
mas_safeAreaLayoutGuideLef
mas_safeAreaLayoutGuideRight
#endif
Masonry框架的主要約束是對NSLayoutAttribute的封裝,與autoLayout的約束基本是一致的,但是由于Masonry的封裝,在使用時(shí),代碼量變得更少,而且更加的簡練直觀。
3、Masonry部分源碼分析
添加約束時(shí),調(diào)用的為mas_makeConstraints,參數(shù)^(MASConstraintMaker *make) 為一個(gè)block,
[redView mas_makeConstraints:^(MASConstraintMaker *make) { }];
在View+MASAdditions.m中,可以看到mas_makeConstraints的實(shí)現(xiàn),主要是MASConstraintMaker的聲明與install工作
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
這里block(constraintMaker)的block,就是前面?zhèn)魅氲腷lock,要搞懂mas_makeConstraints干了什么,我們先看block中做了些什么,前面為redView添加約束時(shí)是這么寫的:
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.mas_equalTo(0);
make.centerX.mas_equalTo(self.view.mas_centerX);
make.width.mas_equalTo(self.view.frame.size.width/3.0);
}];
暫且先不管鏈?zhǔn)秸{(diào)用的原理,這里調(diào)用了top、bottom、mas_equalTo、centerX、width等方法,以非鏈?zhǔn)秸{(diào)用的思路分析Masonry的源碼。
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)centerX {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}
- (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;
}
top、bottom、centerX、width等方法都是類似的,調(diào)用addConstraintWithLayoutAttribute,傳入對應(yīng)的NSLayoutAttribute屬性。在addConstraintWithLayoutAttribute中,通過initWithFirstViewAttribute創(chuàng)建一個(gè)帶有firstViewAttribute的MASViewConstraint對象newConstraint,由于傳入的constraint為nil ,所以執(zhí)行的為將約束添加到self.constraints,并返回newConstraint。
再看mas_equalTo方法:
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (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_equalTo做的做核心的事情是保證之前未設(shè)置過relation的前提下把NSLayoutRelationEqual和參數(shù)attribute傳入了對應(yīng)的layoutRelation 和secondViewAttribute中。secondViewAttribute中包含item和attribute,這些參數(shù)將會在后面調(diào)用autoLayout時(shí)被用到。
再回過頭來看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;
}
這里的install方法的實(shí)現(xiàn),除了基本的邏輯處理,最終通過[constraint install]調(diào)用MASConstraint的install方法。MASConstraint為一個(gè)接口,在非鏈?zhǔn)秸{(diào)用時(shí),我們這里用到的它的實(shí)現(xiàn)類為MASViewConstraint。
MASViewConstraint的install方法的代碼:
- (void)install {
if (self.hasBeenInstalled) {
return;
}
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;
//若不存在secondViewAttribute,則父控件為secondLayoutItem,secontAttribute
//firstLayoutAttribute保持一致
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
//設(shè)置AutoLayout約束
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;
//若存在secondViewAttribute.view,添加約束到最近的父控件
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) {//如果size attribute,添加到則自身
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];
}
}
這里最終就到了真正的autoLayout了,調(diào)用autoLayout設(shè)置約束的主要參數(shù)的來源是前面的top、mas_equalTo等方法賦的值。
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
以上就是再非鏈?zhǔn)秸{(diào)用時(shí),Masonry源碼調(diào)用方法分析。
4、鏈?zhǔn)秸{(diào)用原理分析
如果需要將上下邊距同時(shí)設(shè)置為0,我們可以使用
make.top.bottom.mas_equalTo(0)
如果需要將左邊對齊anotherView的右邊,但同時(shí)保持一定的offset,可以使用
make.left.equalTo(anotherView.mas_right).offset(8)
這種用法就是所謂的鏈?zhǔn)秸{(diào)用。
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
......
留意一個(gè)細(xì)節(jié),top等方法,返回值類型是MASConstraint,而MASConstraintMaker中,定義了一系列的屬性,也就是說,make.top可以視為top屬性的getter方法,而該方法返回的是一個(gè)帶參數(shù)的block,于是可以在block中接受這個(gè)參數(shù),再次調(diào)用left等其它方法,這個(gè)是鏈?zhǔn)秸{(diào)用的基礎(chǔ)。
再看回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;
}
當(dāng)鏈?zhǔn)秸{(diào)用時(shí)如make.top.left時(shí)
- 第一步,make.top,走的為if (!constraint),正常添加約束,并執(zhí)行了newConstraint.delegate = self。
- 第二步,make.top.left,此時(shí),由于第一步執(zhí)行過了newConstraint.delegate = self,constraint不再為nil,進(jìn)入的為下面代碼,將constraint的delegate設(shè)置到compositeConstraint,將constraint存入_childConstraints。
if ([constraint isKindOfClass:MASViewConstraint.class]) {
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
//以新的compositeConstraint替換原來的constraint
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
- (id)initWithChildren:(NSArray *)children {
self = [super init];
if (!self) return nil;
_childConstraints = [children mutableCopy];
for (MASConstraint *constraint in _childConstraints) {
constraint.delegate = self;
}
return self;
}
如果是繼續(xù)鏈?zhǔn)秸{(diào)用,如make.top.left.right,則會有
- 第三步,不再進(jìn)入if ([constraint isKindOfClass:MASViewConstraint.class])和 if (!constraint) ,因?yàn)樵诘诙街幸孕碌腸ompositeConstraint替換原來的constraint,已經(jīng)不再滿足第二步條件了,而是直接返回newConstraint對象,并將將constraint存入_childConstraints。
后面繼續(xù)鏈?zhǔn)秸{(diào)用,都是繼續(xù)執(zhí)行第三步了。
這樣就不斷保存了鏈?zhǔn)秸{(diào)用的約束,知道前面所述的block結(jié)束。
而MASConstraint接口的另一個(gè)實(shí)現(xiàn)類MASCompositeConstraint,即是處理鏈?zhǔn)秸{(diào)用形成的復(fù)合約束的類。
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
在其equalToWithRelation方法中,對self.childConstraints的約束進(jìn)行了遍歷,并調(diào)用了MASConstraint的equalToWithRelation方法進(jìn)行設(shè)置,達(dá)到了對通過鏈?zhǔn)秸{(diào)用添加的各個(gè)約束進(jìn)行設(shè)置的效果。
5、總結(jié)
本文對代碼計(jì)算frame、autoresizing、autoLayout、sizeClass這幾種布局方式做了簡單的概要介紹,并分析了基于autoLayout的Masonry框架源碼以及其鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn)??梢钥闯?,Masonry在鏈?zhǔn)秸{(diào)用、MASConstraint接口等無論在代碼規(guī)范、設(shè)計(jì)模式等方面都做得很好,是一個(gè)相當(dāng)優(yōu)秀的框架。如果是OC純代碼布局的話,使用Masonry框架將會是一個(gè)很好的選擇。
鑒于水平有限,上文難免會有紕漏之處,希望大家能指出文中紕漏或不足之處。