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

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

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

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

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

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

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

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

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

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

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

Tip4:mas_closestCommonSuperview

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

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

Tip5:NSLayoutAttributeLeftMargin是什么

iOS 8新增屬性。下面兩句話等價(jià)!

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

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

//如果有兩條約束,控件的高為60.
//假如你在外部調(diào)用[globalconstraint deactivate],此時(shí)高度就變成了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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,258評論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內(nèi)容