iOS自動布局框架Masonry的使用及其原理

作者: 溫桂龍
部門: 新業(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)

1.jpg
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è)很好的選擇。

鑒于水平有限,上文難免會有紕漏之處,希望大家能指出文中紕漏或不足之處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容