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的使用需要注意的地方

Masonry的更多相关文章

  1. iOS开发之Masonry框架源码深度解析

    Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让 ...

  2. 【原】Masonry+UIScrollView的使用注意事项

    [原]Masonry+UIScrollView的使用注意事项 本文转载请注明出处 —— polobymulberry-博客园 1.问题描述 我想实现的使用在一个UIScrollView依次添加三个UI ...

  3. Xcode8+和iOS10+使用Masonry自动计算行高

    说起tableView的自动计算行高,真的是不想再提了,写了不知道几百遍了.可就是这麽一个小玩意儿,把我给难的不行不行的,眼看都要没头发了. 1.设置tableView的预估行高和行高为自动计算 // ...

  4. Masonry和FDTemplateLayoutCell 结合使用示例Demo

    我们知道,界面布局可以用Storyboard或Xib结合Autolayout实现,如果用纯代码布局,比较热门的有Masonry.SDAutoLayout,下面的简单demo,采用纯代码布局,实现不定高 ...

  5. Masonry介绍与使用实践:快速上手Autolayout

    1 MagicNumber -> autoresizingMask -> autolayout 以上是纯手写代码所经历的关于页面布局的三个时期 在iphone1-iphone3gs时代 w ...

  6. Masonry tableviewCell布局(转)

    转载自:http://www.henishuo.com/masonry-tableviewcell-layout/ 前言 说到iOS自动布局,有很多的解决办法.有的人使用xib/storyboard自 ...

  7. 【原】iOS学习之Masonry第三方约束

    1.Masonry概述 目前最流行的Autolayout第三方框架 用优雅的代码方式编写Autolayout 省去了苹果官方恶心的Autolayout代码 大大提高了开发效率 框架地址:https:/ ...

  8. Masonry使用注意事项

    1 理解自身内容尺寸约束与抗压抗拉 自身内容尺寸约束:一般来说,要确定一个视图的精确位置,至少需要4个布局约束(以确定水平位置x.垂直位置y.宽度w和高度h).但是,某些用来展现内容的用户控件,例如文 ...

  9. iOS AutoLayout自动布局&Masonry介绍与使用实践

    Masonry介绍与使用实践:快速上手Autolayout http://www.cnblogs.com/xiaofeixiang/p/5127825.html http://www.cocoachi ...

  10. iOS开发masonry的一些使用简介

    从一开始的纯代码计算frame,虽然自认为计算frame 刚刚的,但是到后来还是开始xib的自动约束和手动约束与frame搭配使用,经历这几种方式,大概一年前开始普遍使用masonry来代码约束之后也 ...

随机推荐

  1. 限制UITextField输入内容的长度

    一.前言 今天做手机号输入限制长度,例如我的textfield只能输入11位,如果再多输入的话就不再textfield中显示,只显示11位的手机号. 如果用ReactiveCocoa的话,这个很好解决 ...

  2. 基础学习day05---面向对象一类,对象、封装

    一.面向对象   1.1.面向对象的概念    很经典的一句话----------万物皆对象 定义:面向对象一相对面向过程而言的        面向对象和面向过程都是一种思想       面向过程-- ...

  3. python+Eclipse+pydev环境搭建(转)

    编辑器:Python 自带的 IDLE 简单快捷, 学习Python或者编写小型软件的时候.非常有用. 编辑器: Eclipse + pydev插件 1. Eclipse是写JAVA的IDE, 这样就 ...

  4. Swift中的数组

    学习来自<极客学院:Swift中的字符串和集合> 工具:Xcode6.4 直接上基础的示例代码,多敲多体会就会有收获:百看不如一敲,一敲就会 import Foundation //数组: ...

  5. Log4net配置文件一般配置

    配置文件一般配置 <?xml version="1.0"?> <configuration> <configSections> <sect ...

  6. img标签使用默认图片的一种方式

    基于html5提供的onerror这个时间属性.

  7. Windows Server 2012之搭建域控制器DC

    安装域控制器,域(Domain) 1,本地管理员权限 2,设置静态IP 地址 3,至少有一个NTFS分区 4,操作系统版本(web版除外)   设置静态IP地址    dcpromo.exe命令不生效 ...

  8. Effective Java 42 Use varargs judiciously

    Implementation theory The varargs facility works by first creating an array whose size is the number ...

  9. 3.输入三个整数,xyz,最终以从小到大的方式输出。利用嵌套。

    <body>请输入a的值:<input type="numbe" id="a" value=""/>请输入b的值:& ...

  10. MongoDB学习笔记——聚合操作之MapReduce

    MapReduce MongoDB中的MapReduce相当于关系数据库中的group by.使用MapReduce要实现两个函数Map和Reduce函数.Map函数调用emit(key,value) ...