Masonry 源码进阶

Masonry源码阅读配合下面两篇文章足矣。第一篇比较简单,主讲大框架。第二篇比较详细,细节点较多。那我呢?我来讲讲进阶吧。讲一些
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调用了makeConstraints函数
make.top.mas_equalTo(superview.top).offset(padding.top)multipliedBy(1);

读 masonry源码还需要有点语法+block 的基础,读者自行补充。导读开始!show time~


Tip1:Autoresizing

self.translatesAutoresizingMaskIntoConstraints = NO;

self.translatesAutoresizingMaskIntoConstraints = NO;
关闭Autoresizing。 不懂的可以看看这个。如果是 YES,autolayout将无效。
Autoresizing相关 blog

Tip2:make.left.right.top.bottom发生了什么

make.left.right.top.bottom.mas_equalTo(superview)到底发生了什么?一步一步推导!

make 是 MASConstraintMaker
make.left 是MASConstraintMaker的实例对象调用了 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;//设置了代理!
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

make.left.right ,make.left返回的是MASViewAttribute,所以这时候去MASViewAttribute的对象方法里面找 right。它的父类MASConstraint实现了 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 产生的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;
    }
  //下面都不走了!上面已经返回了!
    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;
}

这里调用strongDelegate去做 add 约束的动作。compositeConstraint.delegate = self;strongDelegate就是 make,(make 内心是崩溃的,怎么又是我!)

- (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,所以也不发生!
    }
    if (!constraint) {
      //..不是 nil,不发生。
    }
    return newConstraint;
}

所以make.left.right.top其实就[self.childConstraints addObject:newConstraint];添加了一个新的约束。此时要注意一个细节,return newConstraint;这个细节坑了我 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 这里和上面一样。
这里再次总结下!

make.left->MASViewConstraint  
make.left.right-> MASCompositeConstraint
make.left.right.top-> MASCompositeConstraint
make.left.right.top.bottom-> MASCompositeConstraint
addConstraint这个动作都会在 make 中发生。

最后 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;
    };
}

遍历约束,相对于调用MASViewConstraint的equalTo方法。

Tip3:masory 巧妙架构

这个是对tip2的补充。
  MASConstraintMASViewConstraintMASCompositeConstraint的父类。
  MASCompositeConstraint的重点是有一个存放MASViewConstraintchildConstraints。由于继承了MASConstraint所以又可以调用MASConstraint的所有方法。使链式语法可以继续~
  这里有一个思想!
  父类,子类,子类组。
  子类组用起来和子类没区别,但实际发生链式语法之后,每次都把新生成的子类收集到了自己里面,让自己变大。
  make充当了一个启动器,产生了第一个MASViewConstraint,使后面链式可以跑起来! make 也充当了一个生成MASConstraint生成器的角色,所有的MASConstraint都来自make。这简直太妙了!我水平有限,不知道怎么恰当形容。

Tip4:mas_closestCommonSuperview

寻找共同的父控件到底发生了什么?下面的代码让我一度很困惑。我不能理解!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。确实应该写成寻找共同最小父控件。

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。那布局会出问题。会有一定的误差。这是系统问题。可以看看官方文档

Tip6:优先级

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.
//假如你在外部调用[globalconstraint deactivate],此时高度就变成了30.
//其实这么用起来和 update 差不多。
make.height.equalTo(@30).priorityLow();
globalconstraint = make.height.equalTo(@60);

Tip7:group

我在网上找了很多 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 源码备注。欢迎交流技术!
Masonry源码备注

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容