Masonry 源码进阶

作者: 双手插兜Jeff | 来源:发表于2017-02-23 19:49 被阅读199次

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

    相关文章

      网友评论

        本文标题:Masonry 源码进阶

        本文链接:https://www.haomeiwen.com/subject/funbwttx.html