本篇博客是本人在学习自动布局过程中对自动布局的理解和整理,分三部分介绍,内容可能会有所重复,见谅。
1、springs&struts简介及问题
你肯定很熟悉autosizing masks-也被认为是springs&struts模式。autosizing mask决定了当一个视图的父视图大小改变时,其自身需要做出什么改变。它有一个灵活的或固定不变的margins(struts)吗?它的宽和高要做出什么改变(springs)?
但是毕竟不像自动布局一样对所有视图进行全面约束,在某些情况下的不能按照开发者的意愿完美的布局视图,例如http://www.cocoachina.com/industry/20131203/7462.html中第一个例子就是autosizing布局失败的例子,此时需要在viewWillLayoutSubviews中根据设备方向对frame进行设定,但是适配是一个令人非常烦躁的事情,稍不注意就会出错,所以产生了后来的自动布局。
2、自动布局简介及优势
自动布局使用约束去说明视图间的布局情况,使用约束最大的优势就是你再也不需要把时间浪费在坐标上了。相反,你可以向自动布局描述视图如何和其他视图相关联,自动布局将会为你完成所有困难的工作。这叫做根据目的设计(designing by intent)。
当你根据目的设计时,你表达的是你想要实现什么,而不需要关心它如何实现。在autosizing中我们描述布局时说”button的左上角坐标为(20,230)",现在你可以这么说了:”button是垂直居中于它的父视图,并且相对于父视图的左边缘有一个固定的距离”,使用这个描述,不管父视图多大或多小,自动布局都可以自动计算出你的button需要在哪儿出现,这使得你用户界面的设置更具描述性.你只需简单的定义约束,系统会为你自动计算frames。
使用自动布局另一个重要的好处就是本地化。比如德语中的文本,出了名的比老奶奶的裹脚布还要长,适配起来是一件很麻烦的事。再次,自动布局拯救了猿,因为它能根据label需要显示的内容自动改变label的大小。
自动布局不仅对旋转有作用;它还能轻易的缩放你UI的大小从而适应不同尺寸的屏幕。例如iphone5比iphone4s的高度变高了,程序在iPhone5中显示时是否会出现问题呢,不用担心,自动布局能轻易的拉伸你程序的用户界面,从而充满iPhone5垂直方向上多出来的空间。
注意:标示自动布局是否有效的T-bars是橘黄色时,意味着你的布局没有完成,即自动布局没有足够的约束条件计算出视图的位置和大小。解决办法便是增加更多约束,直到他们变蓝。
3、拥抱约束
1)、自动约束
如果你根本不提供任何约束,Xcode自动分配一套默认的约束,正是我们所知的自动约束。它会在程序built的编译时间中去完成这些事,而不是设计时间。当你设计你的用户界面时,Xcode5中的自动布局为了不参与你的设计方法而努力工作,这这是我们喜欢它的原因。自动约束为你的视图提供一个固定尺寸和位置。换句话说,视图总是拥有跟你在storyboard中看到的一样的坐标。这是非常方便的,因为这就意味着你可以大量的忽视自动布局。你可以为那些拥有充分约束的控件不增加约束,只为那些需要特殊规则的视图创建约束。
2)、Xcode创建自动约束的规则
Xcode只为那些你没有设置任何约束的对象创建自动约束。一旦你增加一个约束,你便是告诉Xcode你接管了这个视图。Xcode将不再增加任何自动约束,并希望你为这个视图增加需要的约束。
3)、不完整约束的影响(约束过多或不足)
虽然Xcode5以后再也不强制你总是有一个有效的布局,但是运行一个无效布局的程序是不明智的,因为自动布局可能不能正确的计算需要将视图放在哪儿,要么视图的位置是不可预知的(约束不够),要么程序将会崩溃(约束过多)。
4)、错位的视图
错位的视图,即根据自动布局显示视图的frame和你在屏幕上放置的视图的frame不在同一位置。此时根据自动布局显示视图的frame是橙色的虚线边框,你在屏幕上放置的视图的frame是橙色的实线边框(当谈到自动布局,橙色代表坏的)。====如果你在屏幕上放置的视图的frame是你想要的位置,此时点击Editor菜单->Resolve Auto Layout Issues菜单->Update Constraints就可以更新约束。
说明:在xib和storyboard中由于苹果将约束可视化呈现给开发者使得自动布局很容易使用;通过代码构建界面时如果想使用自动布局就必须通过苹果提供的接口创建约束,由于自动布局的接口不易使用,所以就产生了masonry,masonry将苹果提供的自动布局接口封装的易于使用。
1、Auto Layout是什么
Auto Layout是一个基于constraint(约束)的布局系统,它根据UI元素之间约束关系来调整UI元素的位置和大小。
2、Auto Layout解决什么问题
3、代码中如何使用Auto Layout
Auto Layout中约束的类对应是NSLayoutConstraint, 而创建NSLayoutConstraint对象主要有两种方式,第一种是
+ (id)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attribute1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2
attribute:(NSLayoutAttribute)attribute2
multiplier:(CGFloat)multiplier
constant:(CGFloat)constant;
上面方法主要意思是,某个view1的attribute1等于(小于或等于/大于或等于)某个view2的attribute2的multiplier倍加上constant。而attribute主要由表示位置(上/下/左/右)和大小(宽/高)的以下几个值:
typedef enum: NSInteger {
NSLayoutAttributeLeft = 1,
NSLayoutAttributeRight,
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,
NSLayoutAttributeTrailing,
NSLayoutAttributeWidth,
NSLayoutAttributeHeight,
NSLayoutAttributeCenterX,
NSLayoutAttributeCenterY,
NSLayoutAttributeBaseline,
NSLayoutAttributeNotAnAttribute = 0
} NSLayoutAttribute;
简化一下,使用公式可以表达为:
view1.attribute1 = view2.attribute2 * multiplier + constant
第二种方式是:
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format
options:(NSLayoutFormatOptions)opts
metrics:(NSDictionary *)metrics
views:(NSDictionary *)views;
这种方式主要是采用Visual Format Language(可视化格式语言)来描述约束布局,虽然语法比较简洁,但是可读性比较差和容易出错。
4、Auto Layout存在问题
虽然Auto Layout在布局view方面是非常强大和灵活,但是创建constraint的语法过于繁杂,引用Masonry一个例子:
UIView *superview = self;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
如此简单的一个例子都要编写这么多行代码,想象一下如果创建多个view的constraint时会多么痛苦啊。另一个方式是采用Visual Format Language (VFL),虽然语法比较简洁,但是可读性比较差和容易出错。
5、为什么使用Masonry
Masonry是采用链式DSL(Domain-specific language)来封装NSLayoutConstraint,通过这种方式编写Auto Layout布局代码更加易读和简洁。
6、Masonry如何使用
使用Masonry创建constraint来定义布局的方式有三种:mas_makeConstraints,mas_updateConstraints,mas_remakeConstraints。
1). mas_makeConstraints
使用mas_makeConstraints创建constraint后,你可以使用局部变量或属性来保存以便下次引用它;如果创建多个constraints,你可以采用数组来保存它们。
// in public/private interface
@property (nonatomic, strong) MASConstraint *topConstraint;
...
// when making constraints
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top);
make.left.equalTo(superview.mas_left).with.offset(padding.left);
}];
...
// then later you can call
[self.topConstraint uninstall];
2). mas_updateConstraints
有时你需要更新constraint(例如,动画和调试)而不是创建固定constraint,可以使用mas_updateConstraints方法
// this is Apple's recommended place for adding/updating constraints
// this method can get called multiple times in response to setNeedsUpdateConstraints
// which can be called by UIKit internally or in your code if you need to trigger an update to your constraints
- (void)updateConstraints {
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
3). mas_remakeConstraints
mas_remakeConstraints与mas_updateConstraints比较相似,都是更新constraint。不过,mas_remakeConstraints是删除之前constraint,然后再添加新的constraint(适用于移动动画);而mas_updateConstraints只是更新constraint的值。
- (void)changeButtonPosition {
[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);
}
}];
}
想了解以上三个代码片段的更多细节,可以下载Masonry iOS Examples工程查阅。
7、masonry使用举例
1)、三个控件等高
make.height.mas_equalTo(@[redView, blueView]);
2)、让控件始终居中显示
make.center.mas_equalTo(self.view);
3)、设置约束的优先级为最低
make.width.height.mas_equalTo(100 * self.scacle).priorityLow();
4)、让控件的宽和高小于或者等于self.view的宽和高
make.width.height.lessThanOrEqualTo(self.view);
8、此外自动布局的第三方库还有Classy,由于没有用过,就不再叙述了,有兴趣的话可以参考http://blog.csdn.net/zhang_red/article/details/45503683,或者谷歌一下。
1)、setNeedsUpdateConstraints
当一个自定义view的某个属性发生改变,并且可能影响到constraint时,需要调用此方法去标记constraints需要在未来的某个点更新,系统然后调用updateConstraints.
2)、needsUpdateConstraints
constraint-based layout system使用此返回值去决定是否需要调用updateConstraints作为正常布局过程的一部分。
3)、updateConstraintsIfNeeded
立即触发约束更新,自动更新布局。
4)、updateConstraints
自定义view应该重写此方法在其中建立constraints. 注意:要在实现在最后调用[super updateConstraints]
5)、updateViewConstraints
自定义UIViewcontroller应该重写此方法在其中建立constraints. 注意:要在实现在最后调用[super updateConstraints]
2、要使用AutoLayout,请先设置要约束的view的translatesAutoresizingMaskIntoConstraints 属性为 NO 。在xib或者sb中勾选Use Auto Layout,所有在xib或者sb中出现的view都已经默认将translatesAutoresizingMaskIntoConstraints设置为NO。
3、在使用AutoLayout布局的view中,代码中避免出现设置其frame相关属性(如center)的代码 ,但是可以获取其frame;
4、通过代码为xib或sb中view增加约束时,尽量避免在 viewDidLoad中执行,最好放在updateViewConstraints[UIViewcontroller]或者updateConstraints[UIView]中 ,记得调用[super updateViewConstraints]或者[super updateConstraints];
注意:在updateViewConstraints为view添加约束,请确保该view的translatesAutoresizingMaskIntoConstraints属性已设置为NO,如果你真的写在viewDidLoad里了,那么可能会遇到这种崩溃错误Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Impossible to set up layout with view hierarchy unprepared for constraint.'
5、 如果需要在控制器中动态添加或者移除视图,在控制器中为新添加的视图增加约束,在updateViewConstrains中实现,然后调用[super updateViewConstrains];同理,在view中动态添加或者移除视图,在updateConstrains中实现,然后调用[super updateConstrains]
6、 控制器在其view需要重新布局时会执行以下过程:
① 控制器的视图调整到新的尺寸 - 控制器会根据当前状态栏、导航条等其它因素的状态来调整其view的位置尺寸
② 如果没有使用autolayout,所有子视图会根据autoresizeing mask调整
③ 调用viewWillLayoutSubviews
④ 调用控制器视图的layoutSubviews,如果是使用autolayout,则会调用updateViewConstrains -> 该方法的实现会调用所有子视图的updateConstraints -> 更新完约束之后,所有视图会根据计算出来的新的布局更新位置
⑤ 调用控制器的viewDidLayoutSubviews
7、自定义view需要重新布局时会执行以下过程:
与使用springs and struts(autoresizingMask)比较,Auto layout在view显示之前,多引入了两个步骤:updating constraints 和laying out views。每一个步骤都依赖于上一个。display依赖layout,而layout依赖updating constraints。 updating constraints->layout->display
第一步:updating constraints,被称为测量阶段,其从下向上(from subview to super view),为下一步layout准备信息。可以通过调用方法setNeedUpdateConstraints去触发此步。constraints的改变也会自动的触发此步。但是,当你自定义view的时候,如果一些改变可能会影响到布局的时候,通常需要自己去通知Auto layout,updateConstraintsIfNeeded。
自定义view的话,通常可以重写updateConstraints方法,在其中可以添加view需要的局部的contraints。
第二步:layout,其从上向下(from super view to subview),此步主要应用上一步的信息去设置view的center和bounds。可以通过调用setNeedsLayout去触发此步骤,此方法不会立即应用layout。如果想要系统立即的更新layout,可以调用layoutIfNeeded。另外,自定义view可以重写方法layoutSubViews来在layout的工程中得到更多的定制化效果。
第三步:display,此步时把view渲染到屏幕上,它与你是否使用Auto layout无关,其操作是从上向下(from super view to subview),通过调用setNeedsDisplay触发,
因为每一步都依赖前一步,因此一个display可能会触发layout,当有任何layout没有被处理的时候,同理,layout可能会触发updating constraints,当constraint system更新改变的时候。
需要注意的是,这三步不是单向的,constraint-based layout是一个迭代的过程,layout过程中,可能去改变constraints,有一次触发updating constraints,进行一轮layout过程。
注意:如果你每一次调用自定义layoutSubviews都会导致另一个布局传递,那么你将会陷入一个无限循环中。