Masonry源碼閱讀

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è)類,MASConstraintMakerMASViewConstraint、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];                                                 \
        }                                                                         \
    }

參考

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

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

  • Masonry是一個(gè)輕量級(jí)的布局框架,擁有自己的描述語法,采用更優(yōu)雅的鏈?zhǔn)秸Z法封裝自動(dòng)布局,簡(jiǎn)潔明了并具有高可讀性...
    3dcc6cf93bb5閱讀 1,946評(píng)論 0 1
  • (一)Masonry介紹 Masonry是一個(gè)輕量級(jí)的布局框架 擁有自己的描述語法 采用更優(yōu)雅的鏈?zhǔn)秸Z法封裝自動(dòng)布...
    木易林1閱讀 2,594評(píng)論 0 3
  • iOS_autoLayout_Masonry 概述 Masonry是一個(gè)輕量級(jí)的布局框架與更好的包裝AutoLay...
    指尖的跳動(dòng)閱讀 1,327評(píng)論 1 4
  • Masonry是一個(gè)輕量級(jí)的布局框架,它擁有自己的描述語法(采用更優(yōu)雅的鏈?zhǔn)秸Z法封裝)來自動(dòng)布局,具有很好可讀性且...
    AngeloD閱讀 3,566評(píng)論 0 9
  • 因?yàn)橹伴_發(fā)時(shí)都是在xib文件中添加約束,或者代碼中計(jì)算frame并沒有接觸過Masonry,現(xiàn)在寫篇博客來歸納總...
    口子窖閱讀 6,614評(píng)論 1 4

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