Masonry 源碼進(jìn)階

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)語法方便的把整個過程封裝了起來。比如

[NSLayoutConstraint constraintWithItem:view1
                            attribute:NSLayoutAttributeTop
                            relatedBy:NSLayoutRelationEqual
                               toItem:superview
                            attribute:NSLayoutAttributeTop
                           multiplier:1.0
                             constant:padding.top]

等價于 Masonry 的。當(dāng)然此時是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。 不懂的可以看看這個。如果是 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,所以這時候去MASViewAttribute的對象方法里面找 right。它的父類MASConstraint實(shí)現(xiàn)了 right 方法

- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
然后
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

此時有一個代理,注意之前的代碼! newConstraint.delegate = self,代理是 make!所以有跑到了這里!

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    
  //這個時候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];//里面就剩一個約束了。
        return compositeConstraint;
    }
  //下面都不走了!上面已經(jīng)返回了!
    if (!constraint) {
        //此處不走!
    }
    return newConstraint;
}

so make.left.right返回了MASCompositeConstraint。里面有兩個MASViewConstraint。MASCompositeConstraint里有childConstraints,里面存放著一個又一個的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 約束的動作。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]) {
      //此時是MASCompositeConstraint,所以也不發(fā)生!
    }
    if (!constraint) {
      //..不是 nil,不發(fā)生。
    }
    return newConstraint;
}

所以make.left.right.top其實(shí)就[self.childConstraints addObject:newConstraint];添加了一個新的約束。此時要注意一個細(xì)節(jié),return newConstraint;這個細(xì)節(jié)坑了我 N 久。這里返回了newConstraint,所以make.left.right.top返回的是MASViewConstraint?不是。這里返回了MASViewConstraint,但是沒有去接收這個約束。MASCompositeConstraint返回的是 self。太狡詐了~~~

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    return self;
}

so make.left.right.top返回MASCompositeConstraint,且添加了一個約束。
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這個動作都會在 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)

這個是對tip2的補(bǔ)充。
??MASConstraintMASViewConstraintMASCompositeConstraint的父類。
??MASCompositeConstraint的重點(diǎn)是有一個存放MASViewConstraintchildConstraints。由于繼承了MASConstraint所以又可以調(diào)用MASConstraint的所有方法。使鏈?zhǔn)秸Z法可以繼續(xù)~
??這里有一個思想!
??父類,子類,子類組。
??子類組用起來和子類沒區(qū)別,但實(shí)際發(fā)生鏈?zhǔn)秸Z法之后,每次都把新生成的子類收集到了自己里面,讓自己變大。
??make充當(dāng)了一個啟動器,產(chǎn)生了第一個MASViewConstraint,使后面鏈?zhǔn)娇梢耘芷饋恚?make 也充當(dāng)了一個生成MASConstraint生成器的角色,所有的MASConstraint都來自make。這簡直太妙了!我水平有限,不知道怎么恰當(dāng)形容。

Tip4:mas_closestCommonSuperview

尋找共同的父控件到底發(fā)生了什么?下面的代碼讓我一度很困惑。我不能理解!closestCommonSuperview && firstViewSuperview怎么可能會為0,后來我意識到firstViewSuperview.superview的父控件是有限的。它最后可能會為 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

然后我測試了下,兩個視圖的父控件不是一個,比如View1的爺控件等于 View2 的父控件,布局也是可以進(jìn)行的。好吧,我 too naive。確實(shí)應(yīng)該寫成尋找共同最小父控件。

Tip5:NSLayoutAttributeLeftMargin是什么

iOS 8新增屬性。下面兩句話等價!

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。那布局會出問題。會有一定的誤差。這是系統(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。我常用的就是這個。

//效果一樣。
make.width.priority(749);
make.width.equalTo(@(10)).priorityLow();

//如果有兩條約束,控件的高為60.
//假如你在外部調(diào)用[globalconstraint deactivate],此時高度就變成了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源碼備注

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

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

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