Masonry 源碼進階

Masonry源碼閱讀配合下面兩篇文章足矣。第一篇比較簡單影钉,主講大框架。第二篇比較詳細掘剪,細節(jié)點較多平委。那我呢?我來講講進階吧夺谁。講一些
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做的事情就用點語法方便的把整個過程封裝了起來。比如

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

等價于 Masonry 的匾鸥。當然此時是view1調(diào)用了makeConstraints函數(shù)
make.top.mas_equalTo(superview.top).offset(padding.top)multipliedBy(1);

讀 masonry源碼還需要有點語法+block 的基礎(chǔ)蜡塌,讀者自行補充。導(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的實例對象調(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實現(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)心是崩潰的,怎么又是我J省)

- (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其實就[self.childConstraints addObject:newConstraint];添加了一個新的約束皂股。此時要注意一個細節(jié)墅茉,return newConstraint;這個細節(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的補充角骤。
??MASConstraintMASViewConstraintMASCompositeConstraint的父類隅忿。
??MASCompositeConstraint的重點是有一個存放MASViewConstraintchildConstraints。由于繼承了MASConstraint所以又可以調(diào)用MASConstraint的所有方法邦尊。使鏈式語法可以繼續(xù)~
??這里有一個思想背桐!
??父類,子類胳赌,子類組牢撼。
??子類組用起來和子類沒區(qū)別,但實際發(fā)生鏈式語法之后疑苫,每次都把新生成的子類收集到了自己里面熏版,讓自己變大。
??make充當了一個啟動器捍掺,產(chǎn)生了第一個MASViewConstraint撼短,使后面鏈式可以跑起來! make 也充當了一個生成MASConstraint生成器的角色挺勿,所有的MASConstraint都來自make曲横。這簡直太妙了!我水平有限不瓶,不知道怎么恰當形容禾嫉。

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 的父控件黍匾,布局也是可以進行的。好吧呛梆,我 too naive锐涯。確實應(yīng)該寫成尋找共同最小父控件。

Tip5:NSLayoutAttributeLeftMargin是什么

iOS 8新增屬性填物。下面兩句話等價全庸!

make.leftMargin.equalTo(10);
make.left.equalTo(another.left).offset(10);

這么用在父控件上當然可以秀仲!但是!

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; 

每一條約束默認都是必須的责语,必須的意思是1000炮障。我常用的就是這個。

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

//如果有兩條約束胁赢,控件的高為60.
//假如你在外部調(diào)用[globalconstraint deactivate],此時高度就變成了30.
//其實這么用起來和 update 差不多白筹。
make.height.equalTo(@30).priorityLow();
globalconstraint = make.height.equalTo(@60);

Tip7:group

我在網(wǎng)上找了很多 group 的用法智末,愣是沒找著。我簡單測試了下徒河。其實 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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市代兵,隨后出現(xiàn)的幾起案子尼酿,更是在濱河造成了極大的恐慌,老刑警劉巖植影,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裳擎,死亡現(xiàn)場離奇詭異,居然都是意外死亡何乎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門土辩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來支救,“玉大人,你說我怎么就攤上這事拷淘「髂” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵启涯,是天一觀的道長贬堵。 經(jīng)常有香客問我恃轩,道長,這世上最難降的妖魔是什么黎做? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任叉跛,我火速辦了婚禮,結(jié)果婚禮上蒸殿,老公的妹妹穿的比我還像新娘筷厘。我一直安慰自己,他們只是感情好宏所,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布酥艳。 她就那樣靜靜地躺著,像睡著了一般爬骤。 火紅的嫁衣襯著肌膚如雪充石。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天霞玄,我揣著相機與錄音祖今,去河邊找鬼坪郭。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的扛吞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼织咧,長吁一口氣:“原來是場噩夢啊……” “哼剃执!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雅任,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤风范,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后沪么,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硼婿,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年禽车,在試婚紗的時候發(fā)現(xiàn)自己被綠了寇漫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡殉摔,死狀恐怖州胳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逸月,我是刑警寧澤栓撞,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響瓤湘,放射性物質(zhì)發(fā)生泄漏瓢颅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一弛说、第九天 我趴在偏房一處隱蔽的房頂上張望挽懦。 院中可真熱鬧,春花似錦剃浇、人聲如沸巾兆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽角塑。三九已至,卻和暖如春淘讥,著一層夾襖步出監(jiān)牢的瞬間圃伶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工蒲列, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窒朋,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓蝗岖,卻偏偏與公主長得像侥猩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抵赢,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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