zoukankan      html  css  js  c++  java
  • Masonry

    Autolayout就像一个知情达理,善解人意的好姑娘,可惜长相有点不堪入目,所以追求者寥寥无几。所幸遇到了化妆大师cloudkite,给她来了一个完美的化妆,从此丑小鸭Autolayout变成了美天鹅Masonry。前几日有幸一见,果然名不虚传,长相甜美,还善解人意。我果断放弃了Frame,开始追求Masonry

    初识Masonry

    初见

    我们先来看看Masonry到底有多美。
    我要设置一个containView,他距离superView的上下左右边距都是10。
    如果我用frame,应该是这样写的:

    UIEdgeInsets edge = UIEdgeInsetsMake(10, 10, 10, 10);
    CGSize superSize = view.superview.frame.size;
    CGFloat width = superSize.width - edge.left - edge.right;
    CGFloat heitht = superSize.height - edge.top - edge.bottom;
    view.frame = CGRectMake(edge.left, edge.top, width, heitht);

    逻辑比较复杂,阅读的时候还得想半天才能想明白,这个frame到底要表达的是什么意思。而且关键的是父View的大小如果改变,还需要再次重新设置Frame。看着Frame这黄脸婆,心里一阵别扭...
    我们来看看充满青春活力的小鲜肉Masonry是怎么样的:

    UIEdgeInsets edge = UIEdgeInsetsMake(10, 10, 10, 10);
    [view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(view.superview).insets(edge);
    }];

    使用mas_makeConstraints在block中对View添加约束。view相对父View的边距为edge。
    代码简单,逻辑一目了然。而且还能跟父View一起调整。简直是perfect,初见Masonry,惊为天人

    细品

    cloudkite给Autolayout披上一层漂亮的外衣之后,将其称为Masonry,但Masonry的本质还是Autolayout。

    Autolayout是什么呢?Autolayout就是给View添加一堆约束,让View在布局的时候通过约束计算出Frame,然后进行布局(Autolayout更多内容见Autolayout的第一次亲密接触)。make.edges.equalTo(view.superview).insets(edge);就是添加约束的过程。

    对于一个约束。他实际表示的是一个不等或者相等关系


     


    用Masonry创建一个完整的约束应该是这样的

    //view1的左边距离父View左边10个点:
    [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(view1.superview.mas_left).multipliedBy(1).offset(10);
    }];

    对应到上图的表达式:
    Item1: make MASConstraintMaker类型,view1的承载对象,表示View1
    Attribute: left 表示左边。left的make的属性。返回值为MASConstraint类型
    Relationship: equalTo 表示"="。equalTo是MASConstraint的属性
    Item2: view1.superview
    Attribute2: mas_left 同样表示左边,mas_left是Masonry给view加的属性,为了不重名,加了mas前缀
    Multiplier: multipliedBy(1) 系数为1
    Constant: offset(10) 常数为10

    Attribute

    MASConstraintMaker

    上面的表达式中,我们可以看到,make是MASConstraintMaker类型。MASConstraintMaker给我们提供了22种Attribute类型

    //Basic Attribute
    @property (nonatomic, strong, readonly) MASConstraint *left;
    @property (nonatomic, strong, readonly) MASConstraint *top;
    @property (nonatomic, strong, readonly) MASConstraint *right;
    @property (nonatomic, strong, readonly) MASConstraint *bottom;
    @property (nonatomic, strong, readonly) MASConstraint *leading;
    @property (nonatomic, strong, readonly) MASConstraint *trailing;
    @property (nonatomic, strong, readonly) MASConstraint *width;
    @property (nonatomic, strong, readonly) MASConstraint *height;
    @property (nonatomic, strong, readonly) MASConstraint *centerX;
    @property (nonatomic, strong, readonly) MASConstraint *centerY;
    @property (nonatomic, strong, readonly) MASConstraint *baseline;
    
    //Margin Attribute
    @property (nonatomic, strong, readonly) MASConstraint *leftMargin;
    @property (nonatomic, strong, readonly) MASConstraint *rightMargin;
    @property (nonatomic, strong, readonly) MASConstraint *topMargin;
    @property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
    @property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
    @property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
    @property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
    @property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;
    
    //Convenient Attribute
    @property (nonatomic, strong, readonly) MASConstraint *edges;
    @property (nonatomic, strong, readonly) MASConstraint *size;
    @property (nonatomic, strong, readonly) MASConstraint *center;

    Attribute总体来说分为三大类

    1. Basic Attribute: 基本属性,支持到iOS6,一般使用得比较多
    2. Margin Attribute: 边缘相关属性,支持到iOS8。由于版本要求比较高,一般用得比较少。Margin相关的详细内容请参考iOS8上关于UIView的Margin新增了3个APIs
    3. Convenient Attribute: 便捷属性,为了使用方便而特意新增的属性。Autolayout本身没有对应的相关属性

    Convenient Attribute实际是基本属性的组合。比如:edges表示left, right, top, bottom。
    下面的两个代码实际的意义是一样的

    //Convenient Attribute
    make.edges.insets(edge);
    
    //Basic Attribute
    make.left.right.top.bottom.insets(edge);

    MASConstraint

    前面我们看到MASConstraintMaker中所有的Attribute都是MASConstraint类型。对于多个Attribute一起写的表达式:

    make.left.right.top.bottom.insets(edge);

    make.left返回的已经是MASConstraint类型,也就是说right这个Attribute是MASConstraint的属性。
    MASConstraint给我们提供了19种Attribute:

    //Basic Attribute
    @property (nonatomic, strong, readonly) MASConstraint *left;
    @property (nonatomic, strong, readonly) MASConstraint *top;
    @property (nonatomic, strong, readonly) MASConstraint *right;
    @property (nonatomic, strong, readonly) MASConstraint *bottom;
    @property (nonatomic, strong, readonly) MASConstraint *leading;
    @property (nonatomic, strong, readonly) MASConstraint *trailing;
    @property (nonatomic, strong, readonly) MASConstraint *width;
    @property (nonatomic, strong, readonly) MASConstraint *height;
    @property (nonatomic, strong, readonly) MASConstraint *centerX;
    @property (nonatomic, strong, readonly) MASConstraint *centerY;
    @property (nonatomic, strong, readonly) MASConstraint *baseline;
    
    //Margin Attribute
    @property (nonatomic, strong, readonly) MASConstraint *leftMargin;
    @property (nonatomic, strong, readonly) MASConstraint *rightMargin;
    @property (nonatomic, strong, readonly) MASConstraint *topMargin;
    @property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
    @property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
    @property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
    @property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
    @property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;

    细看一下,MASConstraint中的Attribute和MASConstraintMaker完全一样。只是MASConstraintMaker中多了3种Convenient Attribute。

    两者Attribute的一致,大大的提升了使用的方便性。使用过程中我们不用再去区分当前属性是MASConstraint还是MASConstraintMaker类型。(事实上没研究他的类型之前,我都不知道他们分别属于2种不同类的属性)

    UIView(12月7日新增)

    我们可以看到在.equalTo(view1.superview.mas_left)里面,superView也有Attribute。我们来看看UIView中有哪些Attribute:

    // Basic Attribute
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_right;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_leading;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_width;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_height;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;
    
    // Margin Attribute
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_leftMargin;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_rightMargin;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_topMargin;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomMargin;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_leadingMargin;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_trailingMargin;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_centerXWithinMargins;
    @property (nonatomic, strong, readonly) MASViewAttribute *mas_centerYWithinMargins;

    可以看出,在UIView中的Attribute和MASConstraint中的几乎一模一样,只是每一个Attribute加了一个mas_前缀。

    由于UIView是系统的类,对其扩展属性和方法一般都需要添加自己的前缀,避免跟原有属性和方法冲突。不过他们的意义跟MASConstraint中的Attribute是相同的

    Relationship

    约束表示的是2个item之间的关系,在Autolayout中一共定义了3种关系:=, >=, <=,对应到Masonry中:

    - (MASConstraint * (^)(id attr))equalTo;
    - (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
    - (MASConstraint * (^)(id attr))lessThanOrEqualTo;

    相等关系我们一般用的多。那么不相等关系我们什么时候用呢?

    假如我有一个Label。Label的长度不能超出父View,如果label中的文字比较短,我希望是文字有多长,Label就有多长。
    由于label具有IntrinsicContentSize属性。所以默认情况下,他是文字有多长,Label就有多长。(更多IntrinsicContentSize的内容参见Autolayout的第一次亲密接触)。所以我们只需要设置Label的长度小于父View即可

    [label mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.offset(0);
        make.centerY.offset(0);
        make.width.lessThanOrEqualTo(label.superview);
    }];

    multiplier

    multiplier表示Attribute前面的乘数。Masonry提供了2种添加multiplier的方法

    //    Sets the NSLayoutConstraint multiplier property
    - (MASConstraint * (^)(CGFloat multiplier))multipliedBy;
    
    //    Sets the NSLayoutConstraint multiplier to 1.0/dividedBy
    - (MASConstraint * (^)(CGFloat divider))dividedBy;

    multipliedBy: 直接设置乘数
    dividedBy: 设置乘数的倒数 multiplier = 1.0/dividedBy
    一般宽或者高的约束使用multiplier比较多

    Constant

    Masonry提供了4种设置constant的方法

    //Modifies the NSLayoutConstraint constant,only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
    - (MASConstraint * (^)(MASEdgeInsets insets))insets;
    
    //Modifies the NSLayoutConstraint constant,only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following NSLayoutAttributeWidth, NSLayoutAttributeHeight
    - (MASConstraint * (^)(CGSize offset))sizeOffset;
    
    //Modifies the NSLayoutConstraint constant, only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
    - (MASConstraint * (^)(CGPoint offset))centerOffset;
    
    //Modifies the NSLayoutConstraint constant
    - (MASConstraint * (^)(CGFloat offset))offset;

    insets: 用来设置left, right, top, bottom。接受MASEdgeInsets类型值
    sizeOffset: 用来设置width, height。接受CGSize类型的值
    centerOffset: 用来设置centerX, centerY。接受CGPoint类型的值
    offset: 可以用来设置所有的东西。接受CGFloat类型的值

    其实一般情况下,我只使用offset....

    小技巧

    1. 如果等式2边的Attribute是一样的,我们可以省略等式右边的Attribute
    2. 如果是等于关系,并且右边的view是父View。连equalTo也可以省略
    3. 如果equalTo里面传的是NSValue类型,效果跟设置offset是一样的
    4. 如果offset为0,其实也是可以省略的...

    下面所有代码实际效果是一样的:

    // 完整的
    make.left.equalTo(view1.superview.mas_left).offset(0);
    
    //省略Attribute的
    make.left.equalTo(view1.superview).offset(0);
    
    //省略equalTo的
    make.left.offset(0);
    
    //使用equalTo替代offset的
    make.left.equalTo(@0);
    
    //终极大招,省略所有的... 可惜会有warning
    make.left;

    不过对于make.left,编译器会报一个警告:你用getter方法获取回来的值未使用,所以不应该使用"."语法


     


    对于这个警告我们可以将返回值转为空消除:

    (void)make.left;

    不过终究又变得麻烦了,又要多写6个字母,愁人...

    设置或更新约束

    对于约束的设置,Masonry提供了3种方法,分别为设置约束、更新约束、重写设置约束

    // 设置约束    
    - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
    
    // 更新约束
    - (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
    
    // 重新设置约束
    - (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;

    mas_makeConstraints: 初次设置约束使用。
    mas_updateConstraints: 更新约束时使用。如果找不着这条约束,会新增,相当于mas_makeConstraints。
    mas_remakeConstraints: 重新设置约束。先将view上所有约束移除,再新增约束

    注意:mas_updateConstraints只能更新已有约束。如果第一次使用的是left, right设置的相对宽度。更新的时候想换成使用width。不能使用mas_updateConstraints,因为已有约束里面没有width的约束,新增width之后会跟原有left, right约束冲突。此时应该使用mas_remakeConstraints

    批量设置约束

    假设有View1,view2,view3三个View,我们想要他们的宽高都等于CGSizeMake(100, 50)。我们可以对他们进行批量设置:

    NSValue *sizeValue = [NSValue valueWithCGSize:CGSizeMake(100, 50)];
    [@[view1,view2,view3] mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.equalTo(sizeValue);
    }];

    由于我们还要设置view的top,left等位置约束。那可不可以在设置位置的mas_makeConstraints里面批量设置宽高呢?实际是可以的!

    //advance set
    [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
        (void)make.top.left;
        make.size.equalTo(@[view2,view3,sizeValue]);
    }];

    不过需要注意的是。设置约束的时候,view一定是已经被addSubview的(详情参考Autolayout的第一次亲密接触),否则会抛异常。所以我们一般在最后一个view上加批量约束

    Priority

    我们知道约束是有优先级的,Masonry给我们提供了4个设置优先级的接口:

     //    Sets the NSLayoutConstraint priority to a float or MASLayoutPriority
    - (MASConstraint * (^)(MASLayoutPriority priority))priority;
    
    //    Sets the NSLayoutConstraint priority to MASLayoutPriorityLow
    - (MASConstraint * (^)())priorityLow;
    
    //    Sets the NSLayoutConstraint priority to MASLayoutPriorityMedium
    - (MASConstraint * (^)())priorityMedium;
    
    //    Sets the NSLayoutConstraint priority to MASLayoutPriorityHigh
    - (MASConstraint * (^)())priorityHigh;

    priority: 可以设置任意的优先级,接受的参数是0-1000的数字
    priorityLow: 设置低优先级,优先级为250
    priorityMedium: 设置中优先级,优先级为500
    priorityHigh: 设置高优先级,优先级为750

    需要注意的是,使用priorityLow、priorityMedium、priorityHigh的时候。不是.priorityHigh,而是.priorityHigh()

    key

    当约束冲突发生的时候,我们经常为找不到是哪个View冲突的而烦恼,这一堆View是个什么东西呀?

    "<MASLayoutConstraint:0x7f8de483fb10 UIView:0x7f8de2f53870.left == UIView:0x7f8de2f586c0.left>",
    "<MASLayoutConstraint:0x7f8de4818b50 UIView:0x7f8de2f53870.right == UIView:0x7f8de2f586c0.right>",
    "<MASLayoutConstraint:0x7f8de4818870 UIView:0x7f8de2f53870.width == 100>",
    "<NSLayoutConstraint:0x7f8de4847e90 UIView:0x7f8de2f586c0.width == 375>"
    
    Will attempt to recover by breaking constraint 
    <MASLayoutConstraint:0x7f8de4818870 UIView:0x7f8de2f53870.width == 100>

    这时候我们可以设置View的key:

    self.view.mas_key = @"self.view";
    view1.mas_key = @"view1";

    设置之后再看一下,哈哈,现在好多了。可以清晰的知道是哪个view了

    "<MASLayoutConstraint:0x7fcd98d17c40 UIView:view1.left == UIView:self.view.left>",
    "<MASLayoutConstraint:0x7fcd98d2b2c0 UIView:view1.right == UIView:self.view.right>",
    "<MASLayoutConstraint:0x7fcd98d2adb0 UIView:view1.width == 100>",
    "<NSLayoutConstraint:0x7fcd98e42050 UIView:self.view.width == 375>"
    
    Will attempt to recover by breaking constraint 
    <MASLayoutConstraint:0x7fcd98d2adb0 UIView:view1.width == 100>

    大家可能会觉得这样一个一个设置,多麻烦啊!别着急,Masonry提供了批量设置的宏MASAttachKeys
    只需要一句代码即可全部设置:

    MASAttachKeys(self.view,view1);

    Shorthand(12月7日新增)

    在写代码的时候,可能你会感觉有的东西要加mas_前缀,有的东西又不用加,代码风格不统一,而且加mas_前缀还麻烦。

    前面介绍过加mas_前缀主要是在扩展系统类的时候为了避免与原有类冲突,这是Apple推荐的做法。不过目前来说,即使不加mas_前缀,也不会有什么问题。所以Masonry提供了不加mas_前缀的方法,只需要你定义几个宏即可。

    1. MAS_SHORTHAND
      定义MAS_SHORTHAND宏之后。可以使用UIView,NSArray中不带mas_前缀的makeConstraints,updateConstraints,remakeConstraints。以及UIView中不带mas_前缀的Attribute。

    2. MAS_SHORTHAND_GLOBALS
      默认的equalTo方法只接受id类型的对象。有时候我们想传入一个CGFloat, CGSize, UIEdgeInsets等。还需要将其转化成NSValue对象,比较麻烦。Masonry也考虑到了这种情况。只需要定义MAS_SHORTHAND_GLOBALS宏。就可以直接对equalTo传入基础类型。Masonry自动转化成NSValue对象

    拨开Masonry的衣服

    Masonry的基本使用方法介绍完了,那么我们来看看Masonry的内部到底有些什么东西?

    结构

    Masonry一共有十三个类,我将这13个类分为5个模块:


     

    Help

    Help模块主要是一些辅助的类。

    NSLayoutConstraint+MASDebugAdditions:这个类的主要作用是重写NSLayoutConstraint的description函数。让约束发生冲突的时候,更易读。如果View或者constraint设置了Key,直接用key的值显示到description中。如果没有设置,显示View或者constraint的指针。

    ViewController+MASAdditions:提供了ViewController的LayoutGuide相关属性,以便View对齐时使用
    MASUtilities:定义了一些公用的宏和属性

    Shorthand

    对于系统原有类(NSArray,UIView)的扩展。Masonry的category方法和属性都加有mas_前缀。这也是Apple建议的做法,避免跟系统原有方法冲突。但是有时候我们可能想用的更方便,不想写mas_前缀(没办法,我就是这么懒...)

    NSArray+MASShorthandAdditionsView+MASShorthandAdditions中定义了不带mas_前缀的扩展。这些扩展根据你是否定义了MAS_SHORTHAND宏来确定是否编译。所以你只需要定义MAS_SHORTHAND宏,就可以方便的使用不带mas_前缀的方法,比如:-[view makeConstraints:]

    Public

    Public模块主要是对外暴露的方法。使用者使用Masonry可以直接接触到。

    NSArray+MASAdditions:主要有定义和更新约束的方法,如mas_makeConstraints:
    View+MASAdditions:除了定义和更新约束的一系列方法之外,还为View增加了mas_top, mas_left等Attribute属性

    Core

    Core模块就是Masonry的核心部分,Masonry的大部分功能都在这4个类里实现

    MASConstraintMaker:约束控制器。控制更新,删除,或者新增约束
    MASConstraint:约束的基类,虚类。定义了Constraint的基本属性和方法。
    MASViewConstraint: 约束的主要实现类。所有对约束使用的功能均在此类中完成
    MASCompositeConstraint:约束的集合类。内部有一个数组,可以保存多个MASViewConstraint。对MASCompositeConstraint调用方法实际等于对其内部的所有MASViewConstraint调用方法

    Property

    此模块主要封装了一些MASConstraint持有的属性。为了使用更方便,或者扩展功能

    MASViewAttribute:每一个Attribute都有一个View与之对应,为了使用更方便,所以将他们通过一个类封装在一起
    MASLayoutConstraint:默认的NSLayoutConstraint是没有Key这个属性的,为了Debug方便。派生一个子类,持有key属性

    实现

    当我们给View添加一个约束的时候到底发生了什么?

    [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.equalTo(view1.superview).offset(20);
    }];

    我们首先来看make.left.top.equalTo(view1.superview).offset(20);

    一、执行"make.left"

    MASConstraintMaker类中有一个属性constraints专门用来存储constraint

    @property (nonatomic, strong) NSMutableArray *constraints;

    当执行make.left的时候, 会将相应的MASConstraint添加到constraints数组中

    - (MASConstraint *)left {
        return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
    }
    
    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
    }    
    
    //核心方法
    - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
        MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    
        //调用make.left.top时走入这里将原来的ViewConstraint替换成MASCompositeConstraint
        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;
        }
    
        // 调用make.left的时候走入这里,将constraint加入到self.constraints中
        if (!constraint) {
            newConstraint.delegate = self;
            [self.constraints addObject:newConstraint];
        }
        return newConstraint;
    }

    对MASConstraintMaker调用Attribute的get方法,最终都会走到-constraint:addConstraintWithLayoutAttribute:中,在这个方法中,通过对应的Attribute生成MASViewConstraint。然后将MASViewConstraint加入到constraints中

    二、执行".top"

    make.left返回的是MASConstraint类型。所以make.left.top是对MASViewConstraint类型调用top方法。

    - (MASConstraint *)top {
        return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
    }
    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
    
        return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    }

    当执行-addConstraintWithLayoutAttribute的时候,ViewConstraint通过delegate又调回到MASConstraintMaker的-constraint:addConstraintWithLayoutAttribute:中。

    在MASConstraintMaker的-constraint:addConstraintWithLayoutAttribute:里,将原来constraints中的MASViewConstraint替换成MASCompositeConstraint。MASCompositeConstraint持有top,left 2个属性。对MASCompositeConstraint做操作时候,其内部的所有属性都会执行相应的操作

    三、执行".equalTo(view1.superview)"

    - (MASConstraint * (^)(id))equalTo {
        return ^id(id attribute) {
            return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
        };
    }
    
    - (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
        return ^id(id attribute, NSLayoutRelation relation) {
            if ([attribute isKindOfClass:NSArray.class]) {
                NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
                NSMutableArray *children = NSMutableArray.new;
                for (id attr in attribute) {
                    MASViewConstraint *viewConstraint = [self copy];
                    viewConstraint.secondViewAttribute = attr;
                    [children addObject:viewConstraint];
                }
                MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
                compositeConstraint.delegate = self.delegate;
                [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
                return compositeConstraint;
            } else {
                NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
                self.layoutRelation = relation;
                self.secondViewAttribute = attribute;
                return self;
            }
        };
    }

    当执行Relationship的方法时,都会走到-equalToWithRelation中。
    在这个方法里面主要是给realationship和secondViewAttribute赋值:

    1. 如果不是数组,直接对realationship和secondViewAttribute赋值
    2. 如果是数组,如:.equalTo(@[view1.mas_left,view2.mas_left]),逻辑上肯定不能是不等关系(>=,<=),所以realationship不用赋值,使用默认值(=)。copy出多个viewConstraint,将secondViewAttribute赋值。然后用多个viewConstraint组成的compositeConstraint替换调原来的viewConstraint。

    四、执行".offset(10)"

    - (MASConstraint * (^)(CGFloat))offset {
        return ^id(CGFloat offset){
            self.offset = offset;
            return self;
        };
    }
    
    - (void)setOffset:(CGFloat)offset {
        self.layoutConstant = offset;
    }

    offset(10)会将10传入到ViewConstraint中,用layoutConstant属性将其存起来。(offset主要影响的是约束里面的constant)

    五、mas_makeConstraints

    看完了make.left.top.equalTo(view1.superview).offset(20);,我们再看看mas_makeConstraints中到底做了什么?

    - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        block(constraintMaker);
        return [constraintMaker install];
    }

    mas_makeConstraints方法很简单,

    1. 将self.translatesAutoresizingMaskIntoConstraints至为NO。translatesAutoresizingMaskIntoConstraints表示是否将设置的Frame转化为约束。当自己设置约束的时候需要将其置为NO
    2. 创建出MASConstraintMaker对象
    3. 通过block抛出到外面设值
    4. constraintMaker install

    上面的代码我们知道,关键的地方还是在于constraintMaker install

    六、constraintMaker install

    - (NSArray *)install {
        if (self.removeExisting) {
            NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
            for (MASConstraint *constraint in installedConstraints) {
                [constraint uninstall];
            }
        }
        NSArray *constraints = self.constraints.copy;
        for (MASConstraint *constraint in constraints) {
            constraint.updateExisting = self.updateExisting;
            [constraint install];
        }
        [self.constraints removeAllObjects];
        return constraints;
    }
    1. 如果需要removeExisting,就把已有的约束remove掉,当调用mas_remakeConstraints的时候会将removeExisting值置为YES
    2. 遍历constraints,调用[constraint install]
    3. 清空constraints,这里的constraintMaker只是一个零时属性,只是一个工具类,不需要存储。所以用完之后就可以将constraints清空

    其实真正关键的地方在[constraint install]

    七、constraint install

    - (void)install {
        // 1. 已经installed的将不做任何操作
        if (self.hasBeenInstalled) {
            return;
        }
    
        //2. 从ViewAttribute中剥离出item和attribute
        MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
        NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
        MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
        NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
    
        //3. 如果没有secondViewAttribute,默认secondItem为其父View,secontAttribute等于firstLayoutAttribute。
        if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
            secondLayoutItem = self.firstViewAttribute.view.superview;
            secondLayoutAttribute = firstLayoutAttribute;
        }
    
        //4. 创建真正用于Autolayout的约束layoutConstraint
        MASLayoutConstraint *layoutConstraint
            = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                            attribute:firstLayoutAttribute
                                            relatedBy:self.layoutRelation
                                               toItem:secondLayoutItem
                                            attribute:secondLayoutAttribute
                                           multiplier:self.layoutMultiplier
                                             constant:self.layoutConstant];
        //5. 将priority和key赋值
        layoutConstraint.priority = self.layoutPriority;
        layoutConstraint.mas_key = self.mas_key;
    
        //6. 找到要添加约束的installView
        if (self.secondViewAttribute.view) {
            MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
            NSAssert(closestCommonSuperview,
                     @"couldn't find a common superview for %@ and %@",
                     self.firstViewAttribute.view, self.secondViewAttribute.view);
            self.installedView = closestCommonSuperview;
        } else if (self.firstViewAttribute.isSizeAttribute) {
            self.installedView = self.firstViewAttribute.view;
        } else {
            self.installedView = self.firstViewAttribute.view.superview;
        }
    
        //7. 添加或更新约束
        MASLayoutConstraint *existingConstraint = nil;
        if (self.updateExisting) {
            existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
        }
        if (existingConstraint) {
            // just update the constant
            existingConstraint.constant = layoutConstraint.constant;
            self.layoutConstraint = existingConstraint;
        } else {
            [self.installedView addConstraint:layoutConstraint];
            self.layoutConstraint = layoutConstraint;
            [firstLayoutItem.mas_installedConstraints addObject:self];
        }
    }
    1. 如果已经installed就不做任何操作
    2. 从ViewAttribute中剥离出item和attribute。前面我们介绍过MASViewAttribute的类主要是将item和attribute2个属性封装在了一起。
    3. secondViewAttribute的值来自于.equalTo(item.attribute)中的item.attribute。当我们写下类似make.left.offset(10);约束的时候,是没有secondViewAttribute的,这时候默认secondItem为其父View,secontAttribute等于firstLayoutAttribute。这就解释了为什么可以这样写make.left.offset(10);
    4. 创建真正用于Autolayout的约束layoutConstraint
    5. 将priority和key赋值
    6. 找到要添加约束的installView。如果是2个View之间的约束,需要寻找这2个View最接近的共同父View。添加约束
    7. 添加或更新约束。当调用mas_updateConstraints的时候updateExisting=YES。这时候会查找是否有已经存在的约束。有就更新,没有就添加。如果是mas_makeConstraints或mas_remakeConstraints,则直接添加

    Extension

    仅仅将代码结构和基本实现过程解析了一下,更多实现细节还需要大家自己去阅读源码

    说实话,Masonry的代码写得真漂亮,不管是代码格式规范,还是设计模式。看起来简直是一种享受。建议大家阅读。

    Autolayout的第一次亲密接触也更新了一些东西。没阅读过或者阅读时间比较早的朋友可以再看看~

    Reference

    Masonry源码
    Autolayout的第一次亲密接触
    iOS8上关于UIView的Margin新增了3个APIs

    原文作者的微、博:

    我的博客
    我的微博

    --------------------简单使用-------------------

    基本的计算公式为

    控件左边 = 参考控件的右边 + 偏移值(5) (控件在参考控件的右边,距离其5px)

    make.left.equalTo(view.superview.mas_right).offset(10);//不填则默认对应left,其他同理

    Masonry有三种设置约束的方法

    mas_makeConstraints //第一次生成约束使用
    
    mas_updateConstraints    //更新其中的约束
    
    mas_remakeConstraints    //重新生成约束,会将之前的所有约束先去掉

    使用注意:在循环cell,如果有代码重复调用的地方,一定要使用mas_remakeConstraints,以此防止循环的时候生成相同的约束,影响性能,甚至,能使用make的地方基本都可以用remake进行代替,防止生成无谓的约束

    简单用法

    初始化一个带边距的view

    UIView *view = [[UIView alloc] init];
    
    view.backgroundColor = [UIColor redColor];
    
    [self.view addSubview:view];//一定要先加入父控件,否则报错
    
    [view mas_makeConstraints:^(MASConstraintMaker *make) {
    
    make.edges.equalTo(view.superview).insets(UIEdgeInsetsMake(20, 20, 20, 20));
    
    }];

    等价

    [view mas_makeConstraints:^(MASConstraintMaker *make) {
    
    make.left.right.top.bottom.equalTo(view.superview).insets(UIEdgeInsetsMake(20, 20, 20, 20));

    left ,right等属性,如字面意思

    等价

    [view mas_makeConstraints:^(MASConstraintMaker *make) {
    
    make.left.equalTo(view.superview).offset(20);
    
    make.top.equalTo(view.superview).offset(20);
    
    make.right.equalTo(view.superview).offset(-20);
    
    make.bottom.equalTo(view.superview).offset(-20);
    
    }];

    链式语法中,and 以及 with都是修饰性语句,不做任何事情,便于理解而已

    make.bottom.and.top.equalTo(view.superview).with.offset(-20);

    源码中

    #pragma mark - Semantic properties
    
    - (MASConstraint *)with {
    
    return self;
    
    }
    
    - (MASConstraint *)and {
    
    return self;
    
    }

    子控件宽高为父控件的一半(multipliedBy)

    [view mas_makeConstraints:^(MASConstraintMaker *make) {
    
    make.left.equalTo(view.superview);
    
    make.top.equalTo(view.superview).offset(20);
    
    make.width.height.equalTo(view.superview).multipliedBy(0.5);
    
    }];

    大于小于

    make.width.greaterThanOrEqualTo(@200);
    
    make.width.lessThanOrEqualTo(@400)

    blcok中进行判断使用约束(在统一处理某些业务的时候)

    [self.button mas_remakeConstraints:^(MASConstraintMaker *make) {
    
    make.size.equalTo(self.buttonSize);
    
    if (topLeft) {
    
    make.top.and.left.offset(10);
    
    } else {
    
    make.bottom.and.right.offset(-10);
    
    }
    
    }];死高度300 * 300

    修改指定约束

    MASConstraint *topConstraint;
    
    // 在生成约束的时候
    
    [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    
    topConstraint = make.top.equalTo(superview.mas_top);
    
    make.left.equalTo(superview.mas_left);
    
    }];
    
    ...
    
    // 在之后进行对该约束 进行修改
    
    [topConstraint uninstall];

    写死高度300 * 300

    [view mas_makeConstraints:^(MASConstraintMaker *make) {
    
    make.center.equalTo(view.superview);
    
    make.width.height.equalTo(@300);
    
    }];

    关于mas_equalTo使用

    Masonry表示相等有两种方法,equalTo 和 mas_equalTo

    mas_equalTo其实是多了一层处理的宏而已,因为equalTo并不支持基本数据类型

    #define mas_equalTo(...)                equalTo(MASBoxValue((__VA_ARGS__)))

    在高度为300的约束中,可以这样子写

    mak.height.equalTo(@300);

    也可以,使用mas_equalTo,一般情况下,我会全部使用mas_equalTo来处理基本数据类型的封装

    mak.height.mas_equalTo(300);

    并列排序-水平 或者 高度

    经常会遇到很多需要等宽或者登高排序的需求,下面是我个人使用的一种方法,可以参考一下,但是需要说明的是,相对布局的各种用法很多,请思考便可以,同一种效果,N种写法

    - (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //都是相对于suerpview来设置位置的
    
    NSMutableArray *viewArray = [NSMutableArray array];
    
    NSArray *colorArray = @[[UIColor redColor],[UIColor blueColor],[UIColor orangeColor],[UIColor purpleColor]];
    
    for (int i = 0; i < colorArray.count ; i++) {
    
    UIView *view = [[UIView alloc] init];
    
    view.backgroundColor = colorArray[i];
    
    [self.view addSubview:view];
    
    [viewArray addObject:view];
    
    }
    
    [self sortVerticalWithViews:viewArray LeftMargin:50 Width:100 BackViewHeight:300];
    
    viewArray = [NSMutableArray array];
    
    for (int i = 0; i < colorArray.count ; i++) {
    
    UIView *view = [[UIView alloc] init];
    
    view.backgroundColor = colorArray[i];
    
    [self.view addSubview:view];
    
    [viewArray addObject:view];
    
    }
    
    [self sortHorizontalWithViews:viewArray TopMargin:320 TopView:self.view Height:100];
    
    }
    
    #pragma mark 将控件进行排序,更新其操作(水平)
    
    - (void)sortHorizontalWithViews:(NSArray *)views TopMargin:(NSInteger)topMargin TopView:(UIView *)topView Height:(NSInteger)viewH
    
    {
    
    __block UIView *leftView;
    
    [views enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    
    UIView *tempView = obj;
    
    [tempView mas_remakeConstraints:^(MASConstraintMaker *make) {
    
    if (idx == 0) {
    
    make.left.equalTo(tempView.superview);
    
    if ([topView isEqual:tempView.superview]) { //如果传入的是容器view 则上方无控件
    
    make.top.mas_equalTo(topView).offset(topMargin);
    
    } else {
    
    make.top.mas_equalTo(topView.mas_bottom).offset(topMargin);
    
    }
    
    make.width.mas_equalTo(tempView.superview.mas_width).multipliedBy((CGFloat)1 / views.count);
    
    make.height.mas_equalTo(viewH);
    
    } else {
    
    make.left.equalTo(leftView.mas_right);
    
    make.top.mas_equalTo(leftView);
    
    make.width.mas_equalTo(leftView);
    
    make.height.equalTo(leftView);
    
    }
    
    }];
    
    leftView = tempView;
    
    }];
    
    }
    
    #pragma mark 将控件进行排序,更新其操作(垂直)
    
    - (void)sortVerticalWithViews:(NSArray *)views LeftMargin:(NSInteger)leftMargin Width:(NSInteger)viewWidth BackViewHeight:(NSInteger)backViewHeight
    
    {
    
    __block UIView *topView;
    
    [views enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    
    UIView *tempView = obj;
    
    [tempView mas_remakeConstraints:^(MASConstraintMaker *make) {
    
    if (idx == 0) {
    
    make.left.equalTo(tempView.superview).offset(leftMargin);
    
    make.top.mas_equalTo(tempView.superview);
    
    make.width.mas_equalTo(viewWidth);
    
    make.height.mas_equalTo(backViewHeight / views.count);
    
    } else {
    
    make.left.equalTo(topView);
    
    make.top.mas_equalTo(topView.mas_bottom);
    
    make.width.mas_equalTo(topView);
    
    make.height.mas_equalTo(topView);
    
    }
    
    }];
    
    topView = tempView;
    
    }];
    
    }

    这里写图片描述

    动画问题

    动画问题,和普通的方法实现差不多,重点只是修改约束后调用

    [view.superview layoutIfNeeded];

    而已

    [view mas_makeConstraints:^(MASConstraintMaker *make) {
    
    make.top.mas_equalTo(400);
    
    make.left.mas_equalTo(100);
    
    make.size.mas_equalTo(CGSizeMake(100, 100));
    
    }];
    
    [view.superview layoutIfNeeded];//如果其约束还没有生成的时候需要动画的话,就请先强制刷新后才写动画,否则所有没生成的约束会直接跑动画
    
    [UIView animateWithDuration:3 animations:^{
    
    [view mas_updateConstraints:^(MASConstraintMaker *make) {
    
    make.left.mas_equalTo(200);
    
    }];
    
    [view.superview layoutIfNeeded];//强制绘制
    
    }];

    Cell的高度计算

    借鉴@星光社的戴铭 的方法 [AutoLayout框架Masonry使用心得]
    (http://www.jianshu.com/p/24e4ff56bfea)
    也可以参考forkingdog的FDTemplateLayoutCell

    • iOS8 以上
    tableView.rowHeight = UITableViewAutomaticDimension;
    tableView.estimatedRowHeight = 80; //减少第一次计算量,iOS7后支持
    
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        return UITableViewAutomaticDimension;//返回即可
    }
    • iOS7
    //在model中添加属性缓存高度
    @interface DataModel : NSObject
    @property (copy, nonatomic) NSString *text;
    @property (assign, nonatomic) CGFloat cellHeight; //缓存高度
    @end
    
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        static CustomCell *cell;
        //只初始化一次cell
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([CustomCell class])];
        });
        DataModel *model = self.dataArray[(NSUInteger) indexPath.row];
    
        if (model.cellHeight <= 0) {
            [cell makeupData:model];
            //使用systemLayoutSizeFittingSize获取高度
            model.cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 1;
        }
        return model.cellHeight;
    }

    遇到的问题

    AutoLayout是在iOS7 之后才刚刚推出的一种新的方法,因为在iOS7系统上并不能算得上十分完善,经常有一些bug,然而在iOS8中相对好的处理了

    • 最明显的问题就是scorllView的contentSize问题

    • 系统的约束并不是在设置完成之后里面进行绘图的,而是在最后ViewDidApper()这个函数前一些时间才完成绘图,而且每次绘制(比如,往scorllView上添加新的子控件,即增加了新的约束)后,scorllView的contentOffset和contentSize都会初始化为0,因此每次都需要重新设置(如果你在绘制前已经设置了contentSize的话),或者你可以使用tableView来代替scorllView

    - (void)viewDidAppear:(BOOL)animated
    
    {
    
    [super viewDidAppear:animated];
    
    _scorllView.contentSize = CGSizeMake(200, 200);
    
    }
    • 如果你需要在约束设置完成后立马得到frame的数值的话,调用
    [view.superview layoutIfNeeded];

    之后会强制性更新约束,这句话之后便可以得到frame,在iOS8中只需要在这加入设置contentSize便可以实现正常的scrollView滚动,而iOS7中则不可以,请注意。

    而且如果出现什么疑难杂症的话,基本都是AutoLayout在iOS的不适用,所以搜索问题的话,各位直接搜索Autolayout 关键字便可,不必搜索Masonry关键字的问题(反正也搜不到什么答案...)

    • contentView的冲突
    • 如果遇到和contentView的冲突,基本原因是因为cell的content view有一个系统的约束(高度),而masonry是不会去管理非自己产生的约束,因此在使用label imageview等情况下,增加以下属性设置,确保优先级以防止冲突
    [_contentLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

    随笔

    之所以开始写blog,只因为,梳理blog的过程中,会让自己懂得更多。

    最后附上[不定期更新]

    看到比较好的blog, 在此附上, 向各位大神学习(串哥好像还出了直播讲解masonry,等视频上传了去观摩下)

    里脊串的开发随笔 - 《Masonry介绍与使用实践(快速上手Autolayout)》

    里脊串的开发随笔 - 《如何使用Masonry设计复合型cell》

    星光社的戴铭 - 《AutoLayout框架Masonry使用心得》

    小笨狼 -- 《追求Masonry》

    相关链接:

    追求Masonry

    如何使用masonry设计复合型cell

    iOS Autolayout之Masonry解读

    Masonry用法总结

    Masonry整体动画更新约束

    Masonry的使用,动画,出现问题解决等

    iOS开发笔记--使用Auto Layout中的VFL(Visual format language)--代码实现自动布局

    iOS Masonry的使用需要注意的地方

  • 相关阅读:
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
  • 原文地址:https://www.cnblogs.com/On1Key/p/5452499.html
Copyright © 2011-2022 走看看