Autolayout就像一个知情达理,善解人意的好姑娘,惋惜长相有点不堪入目,因此追求者寥寥无几。所幸遇到了化妆大师cloudkite
,给她来了一个完美的化妆,今后丑小鸭Autolayout变成了美天鹅Masonry。前几日有幸一见,果真名不虚传,长相甜美,还善解人意。我果断放弃了Frame,开始追求Masonryphp
咱们先来看看Masonry到底有多美。
我要设置一个containView,他距离superView的上下左右边距都是10。
若是我用frame,应该是这样写的:css
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是怎么样的:html
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,惊为天人git
cloudkite
给Autolayout披上一层漂亮的外衣以后,将其称为Masonry,但Masonry的本质仍是Autolayout。github
Autolayout是什么呢?Autolayout就是给View添加一堆约束,让View在布局的时候经过约束计算出Frame,而后进行布局(Autolayout更多内容见Autolayout的第一次亲密接触)。make.edges.equalTo(view.superview).insets(edge);
就是添加约束的过程。sql
对于一个约束。他实际表示的是一个不等或者相等关系swift
用Masonry建立一个完整的约束应该是这样的设计模式
//view1的左边距离父View左边10个点: [view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(view1.superview.mas_left).multipliedBy(1).offset(10); }];
对应到上图的表达式:Item1
: make MASConstraintMaker类型,view1的承载对象,表示View1Attribute
: left 表示左边。left的make的属性。返回值为MASConstraint类型Relationship
: equalTo 表示"="。equalTo是MASConstraint的属性Item2
: view1.superviewAttribute2
: mas_left 一样表示左边,mas_left是Masonry给view加的属性,为了避免重名,加了mas前缀Multiplier
: multipliedBy(1) 系数为1Constant
: offset(10) 常数为10api
上面的表达式中,咱们能够看到,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整体来讲分为三大类
Convenient Attribute实际是基本属性的组合。好比:edges表示left, right, top, bottom。
下面的两个代码实际的意义是同样的
//Convenient Attribute make.edges.insets(edge); //Basic Attribute make.left.right.top.bottom.insets(edge);
前面咱们看到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种不一样类的属性)
咱们能够看到在.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是相同的
约束表示的是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表示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比较多
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....
下面全部代码实际效果是同样的:
// 完整的 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上加批量约束
咱们知道约束是有优先级的,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
: 设置低优先级,优先级为250priorityMedium
: 设置中优先级,优先级为500priorityHigh
: 设置高优先级,优先级为750
须要注意的是,使用priorityLow、priorityMedium、priorityHigh的时候。不是.priorityHigh
,而是.priorityHigh()
当约束冲突发生的时候,咱们常常为找不到是哪一个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);
在写代码的时候,可能你会感受有的东西要加mas_前缀,有的东西又不用加,代码风格不统一,并且加mas_前缀还麻烦。
前面介绍过加mas_前缀主要是在扩展系统类的时候为了不与原有类冲突,这是Apple推荐的作法。不过目前来讲,即便不加mas_前缀,也不会有什么问题。因此Masonry提供了不加mas_前缀的方法,只须要你定义几个宏便可。
MAS_SHORTHAND
定义MAS_SHORTHAND宏以后。可使用UIView,NSArray中不带mas_前缀的makeConstraints,updateConstraints,remakeConstraints。以及UIView中不带mas_前缀的Attribute。
MAS_SHORTHAND_GLOBALS
默认的equalTo方法只接受id类型的对象。有时候咱们想传入一个CGFloat, CGSize, UIEdgeInsets等。还须要将其转化成NSValue对象,比较麻烦。Masonry也考虑到了这种状况。只须要定义MAS_SHORTHAND_GLOBALS宏。就能够直接对equalTo传入基础类型。Masonry自动转化成NSValue对象
Masonry的基本使用方法介绍完了,那么咱们来看看Masonry的内部到底有些什么东西?
Masonry一共有十三个类,我将这13个类分为5个模块:
Help模块主要是一些辅助的类。
NSLayoutConstraint+MASDebugAdditions
:这个类的主要做用是重写NSLayoutConstraint的description函数。让约束发生冲突的时候,更易读。若是View或者constraint设置了Key,直接用key的值显示到description中。若是没有设置,显示View或者constraint的指针。
ViewController+MASAdditions
:提供了ViewController的LayoutGuide相关属性,以便View对齐时使用MASUtilities
:定义了一些公用的宏和属性
对于系统原有类(NSArray,UIView)的扩展。Masonry的category方法和属性都加有mas_前缀。这也是Apple建议的作法,避免跟系统原有方法冲突。可是有时候咱们可能想用的更方便,不想写mas_前缀(没办法,我就是这么懒...)
在NSArray+MASShorthandAdditions
和View+MASShorthandAdditions
中定义了不带mas_前缀的扩展。这些扩展根据你是否认义了MAS_SHORTHAND宏来肯定是否编译。因此你只须要定义MAS_SHORTHAND宏,就能够方便的使用不带mas_前缀的方法,好比:-[view makeConstraints:]
Public模块主要是对外暴露的方法。使用者使用Masonry能够直接接触到。
NSArray+MASAdditions
:主要有定义和更新约束的方法,如mas_makeConstraints:View+MASAdditions
:除了定义和更新约束的一系列方法以外,还为View增长了mas_top, mas_left等Attribute属性
Core模块就是Masonry的核心部分,Masonry的大部分功能都在这4个类里实现
MASConstraintMaker
:约束控制器。控制更新,删除,或者新增约束MASConstraint
:约束的基类,虚类。定义了Constraint的基本属性和方法。MASViewConstraint
: 约束的主要实现类。全部对约束使用的功能均在此类中完成MASCompositeConstraint
:约束的集合类。内部有一个数组,能够保存多个MASViewConstraint。对MASCompositeConstraint调用方法实际等于对其内部的全部MASViewConstraint调用方法
此模块主要封装了一些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);
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中
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作操做时候,其内部的全部属性都会执行相应的操做
- (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赋值:
.equalTo(@[view1.mas_left,view2.mas_left])
,逻辑上确定不能是不等关系(>=,<=),因此realationship不用赋值,使用默认值(=)。copy出多个viewConstraint,将secondViewAttribute赋值。而后用多个viewConstraint组成的compositeConstraint替换调原来的viewConstraint。- (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)
看完了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方法很简单,
上面的代码咱们知道,关键的地方仍是在于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; }
其实真正关键的地方在[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]; } }
.equalTo(item.attribute)
中的item.attribute
。当咱们写下相似make.left.offset(10);
约束的时候,是没有secondViewAttribute的,这时候默认secondItem为其父View,secontAttribute等于firstLayoutAttribute。这就解释了为何能够这样写make.left.offset(10);
仅仅将代码结构和基本实现过程解析了一下,更多实现细节还须要你们本身去阅读源码
说实话,Masonry的代码写得真漂亮,无论是代码格式规范,仍是设计模式。看起来简直是一种享受。建议你们阅读。
Autolayout的第一次亲密接触也更新了一些东西。没阅读过或者阅读时间比较早的朋友能够再看看~
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 //从新生成约束,会将以前的全部约束先去掉
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; }
[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)
[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];
[view mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(view.superview); make.width.height.equalTo(@300); }];
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];//强制绘制 }];
借鉴@星光社的戴铭 的方法 [AutoLayout框架Masonry使用心得]
(http://www.jianshu.com/p/24e4ff56bfea)
也能够参考forkingdog的FDTemplateLayoutCell
tableView.rowHeight = UITableViewAutomaticDimension; tableView.estimatedRowHeight = 80; //减小第一次计算量,iOS7后支持 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return UITableViewAutomaticDimension;//返回便可 }
//在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); }
[view.superview layoutIfNeeded];
以后会强制性更新约束,这句话以后即可以获得frame,在iOS8中只须要在这加入设置contentSize即可以实现正常的scrollView滚动,而iOS7中则不能够,请注意。
并且若是出现什么疑难杂症的话,基本都是AutoLayout在iOS的不适用,因此搜索问题的话,各位直接搜索Autolayout 关键字即可,没必要搜索Masonry关键字的问题(反正也搜不到什么答案...)
[_contentLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
之因此开始写blog,只由于,梳理blog的过程当中,会让本身懂得更多。
看到比较好的blog, 在此附上, 向各位大神学习(串哥好像还出了直播讲解masonry,等视频上传了去观摩下)
里脊串的开发随笔 - 《Masonry介绍与使用实践(快速上手Autolayout)》
里脊串的开发随笔 - 《如何使用Masonry设计复合型cell》
星光社的戴铭 - 《AutoLayout框架Masonry使用心得》
相关连接:
iOS开发笔记--使用Auto Layout中的VFL(Visual format language)--代码实现自动布局