最近开始看WWDC的视频,复习了一下AutoLayout的基础内容,写一点儿总结吧,免得看完又忘。
AutoLayout
AutoLayout是基于约束的,描述性的布局系统。使用约束来描述布局,view的frames会依据这些约束自动进行计算。
AutoLayout是苹果在iOS6中引入的用来替换之前的“Springs&Struts”布局模型的新布局系统。
“Spring&Struts”是基于frame的布局,它在大部分情况下还是有用的,但是随着4寸iPhone5的发布带来的大屏适配的工作以及在横竖屏切换时经常需要在
viewWillLayoutSubviews
方法中编写大量布局代码。相比之下,AutoLayout不仅可以完成“Spring&Struts”提供的功能,还提供了其所没有的特性:
- AutoLayout可以指定任意两个view的相对位置,而不需要像Autoresizing Mask那样需要两个view在直系的view hierarchy中。
- AutoLayout不必须指定相等关系的约束,它可以指定非相等约束(大于或者小于等),而Autoresizing Mask所能做的布局只能是相等条件的。
-
AutoLayout可以指定约束的优先级,计算frame时将优先按照满足优先级高的条件进行计算。
如何添加约束
通常我们直接在IB中设置约束,这里着重记录下如何用代码添加约束:
当在代码中创建视图和它们的约束条件时候,一定要记得将 translatesAutoResizingMaskIntoConstraints
属性设置为 NO。
- 我们可以使用
NSLayoutConstraint
的类方法:+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c
创建约束对象。
- 注意:上面方程的等号表示的是相等关系,而不是赋值。当自动布局求解这些方程时,并不是将等式右边的值赋值给等式左边。相反,它同时计算属性 1 和属性 2 的值使它们之间的关系成立。
- 使用
UIView
对象的实例方法:-(void)addConstraint:(NSLayoutConstraint *)constraint
将约束添加到view上。
将约束添加到view上时要注意:
-
对于两个同层级view之间的约束关系,添加到他们的父view上
-
对于两个不同层级view之间的约束关系,添加到他们最近的共同父view上
-
对于有层次关系的两个view之间的约束关系,添加到层次较高的父view上
- 可以通过
-setNeedsUpdateConstraints
(在下一次绘制循环中触发重新布局)和-layoutIfNeeded
(强制系统立即更新视图树的布局)两个方法来刷新约束的改变,使UIView重新布局。这和CoreGraphic的-setNeedsDisplay
一套东西是一样的。
布局过程
- 更新约束:这是自下而上(从子视图到父视图)发生的,它为布局准备好必要的信息,而这些布局将在实际设置视图的 frame 时被传递过去并被使用。你可以通过调用
-setNeedsUpdateConstraints
来触发这个操作,同时,你对约束条件系统做出的任何改变都将自动触发这个方法。 - 布局:这是个自上而下(从父视图到子视图)的过程,这种布局操作实际上是通过设置 frame(在 OS X 中)或者 center 和 bounds(在 iOS 中)将约束条件系统的解决方案应用到视图上。你可以通过调用
-setNeedsLayout
来触发一个操作请求,这并不会立刻应用布局,而是在稍后再进行处理。因为所有的布局请求将会被合并到一个布局操作中去,所以你不需要为经常调用这个方法而担心。 - 显示:显示器都会自上而下将渲染后的视图传递到屏幕上,你也可以通过调用
-setNeedsDisplay
来触发,这将会导致所有的调用都被合并到一起推迟重绘。重写熟悉的 drawRect:能够让我们获得自定义视图中显示过程的所有权。
每一步都是依赖前一步操作的,如果有任何布局的变化还没实行的话,显示操作将会触发一个布局行为。类似地,如果约束条件系统中存在没有实行的改变,布局变化也将会触发更新约束条件。
但是这三步并不是单向的。基于约束条件的布局是一个迭代的过程,布局操作可以基于之前的布局方案来对约束做出更改,而这将再次触发约束的更新,并紧接另一个布局操作。这可以被用来创建高级的自定义视图布局,但是如果你每一次调用的自定义 -layoutSubviews
都会导致另一个布局操作的话,你将会陷入到无限循环的麻烦中去。
控制布局
- 通常我们需要在
-updateConstraints
方法中集中添加约束,并且确保在你的实现中增加任何你需要布局子视图的约束条件之后,调用一下[super updateConstraints]
,在这个方法中,你不会被允许禁用何约束条件,因为你已经进入上面所描述的布局过程的第一步了。
如果稍后一个失效的约束条件发生了改变的话,你需要立刻移除这个约束并调用-setNeedsUpdateConstraints
事实上,仅在这种情况下你需要触发更新约束条件的操作。 - 如果你不能利用布局约束条件达到子视图预期的布局,你可以进一步重写
-layoutSubviews
,通过这种方式当约束条件系统得到解决并且结果将要被应用到视图中时,你便已经进入到布局过程的第二步了。
Intrinsic Content Size
一些视图依据给定的内容有一个原生的大小,这就称为它们的固有内容尺寸。并不是所有视图都有固有内容尺寸:
为了在自定义视图中实现固有内容尺寸,你需要做两件事:重写
-intrinsicContentSize
为内容返回恰当的大小,无论何时有任何会影响固有内容尺寸的改变发生时,调用 invalidateIntrinsicContentSize
。如果这个视图只有一个方向的尺寸设置了固有尺寸,那么为另一个方向的尺寸返回 UIViewNoIntrinsicMetric/NSViewNoIntrinsicMetric
。
注意当为了填充一个空间需要拉伸所有视图时,如垂直摆放的两个textview,如果它们都有一个相同的内容压缩优先级,这个布局就会是有歧义的。自动布局不知道该拉伸哪个视图,这时就应该调整某个textview的content-hugging priority与 compression-resistance priority。
Alignment Rect
自动布局并不会操作视图的 frame,但能作用于视图的 alignment rect,在很多情况下,它们是相同的。你可以通过重写
alignmentRectForFrame:
和 frameForAlignmentRect
这两个方法在frame与alignment rect之间转换。
当你要自定义一个控件的时候可以重写
alignmentRectInsets
方法,这个方法允许你返回相对于 frame 的 edge insets。
Debugging
对于不能确定的布局,可以通过调试时暂停程序,在debugger中输入
(lldb)po [[UIWindow keyWindow] _autolayoutTrace]
来遍历视图层,检查错误。检查是否有ambiguity:
(lldb)po [view hasAmbiguousLayout]
哪里发生了ambiguous:
(lldb)po [view excerciseAmbiguousInLayout]
动画
- CoreAnimation:
[UIView animateWithDuration:0.5 animations:^{
[view layoutIfNeeded];
}];
请注意,使用这种方法,你可以对约束条件做出的改变并不局限于约束条件的常量。你可以删除约束条件,增加约束条件,甚至使用临时动画约束条件。由于新的约束只被解释一次来决定新的 frames,所以更复杂的布局改变都是有可能的。
需要记住的是:Core Animation 和 Auto Layout 结合在一起产生视图动画时,自己不要接触视图的 frame。一旦视图使用自动布局,那么你已经将设置 frame 的责任交给了布局系统。你的干扰将造成怪异的行为。
- 直接对约束条件做动画
约束条件一旦创建后,只有其常量可以被改变。
UIStackView
StackView是iOS9中提供的一种简单布局控件,剔除了复杂的约束,利用自动布局的强大来布局界面,单个 StackView 由一行或者一列控件组成,StackView 根据设置的对齐,间距和大小属性来决定subviews的位置。
同时,StackView也可嵌套来构建更复杂的布局。
AutoLayout和UIScrollView
在使用scroll view时,不仅要定义它的大小跟位置,还要确定它的内容的大小!可按如下步骤来对scroll view进行布局:
Your layout must fully define the size of the content view (except where defined in steps 5 and 6).To set the height based on the intrinsic size of your content, you must have an unbroken chain of constraints and views stretching from the content view’s top edge to its bottom edge.
If your content does not have an intrinsic content size, you must add the appropriate size constraints, either to the content view or to the content.
AutoLayout和Self-Sizing Table View Cells
在iOS8之后,可以下面这种简单的方式来对动态变化的Table View Cell的高度进行自动计算:
tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableViewAutomaticDimension
当然,前提是你的cell中的内容的约束已经从上至下形成一个闭环。AutoLayout可由此可以推算出cell的高度。
参考资料
- WWDC 2012 Session 202 Introduction to Auto Layout for iOS and OS X
- WWDC 2012 Session 228 Best Practices for Mastering Auto Layout
- WWDC 2012 Session 232 Auto Layout by Example
- WWDC 2012 Session笔记——202, 228, 232 AutoLayout(自动布局)入门
- 先进的自动布局工具箱
- Auto Layout Guide
- Auto Layout Tutorial in iOS 9 Part 1: Getting Started
- Auto Layout Tutorial in iOS 9 Part 2: Constraints