闻道丶iOS(大杂烩)

Masonry 源码进阶

2017-02-23  本文已影响199人  双手插兜Jeff

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源码备注

上一篇下一篇

猜你喜欢

热点阅读