注:本文翻译自Changing Constraints
对一个约束进行所有的改变实质上就是修改该约束的数学表达式(见图17-1)。你可以在Anatomy of a Constraint中查看更多关于约束方程的信息。
图17-1 约束方程式
下列所有行为都会改变一个或多个约束:
- 激活或失效约束
- 修改约束的常数值(constant value)
- 修改约束的优先级
- 从视图层级中移除视图
其他改变,如设置控件的属性,或是修改视图层级,都可以改变约束。当改变发生时,系统会预设置出一个延迟布局阶段(查看The Deferred Layout Pass)。
一般来说,你可以在任意时刻设置这些修改。理想情况下,大多数约束都会在Interface Builder中设置,或者是在ViewController的初始化配置时(如viewDidLoad)通过代码创建。如果你需要在运行时动态修改约束,通常最好的方式就是在应用程序状态变化时进行修改。例如,如果你想修改一个约束来响应按钮的点击操作,那就直接在该按钮的动作方法中进行修改。
有时候,为了优化性能,你可能需要分阶段进行一系列的修改。要了解更多信息,请查看Batching Changes。
延迟布局阶段(The Deferred Layout Pass)
为了避免直接更新受影响视图的frame,Auto Layout设置了一个稍后执行的布局阶段。此延迟阶段首先更新布局的约束,之后为视图层级中的所有视图计算frame。
你可以通过调用setNeedsLayout或setNeedsUpdateConstraints方法设置自己的延迟布局阶段。
视图层级中的延迟布局阶段通常由两个阶段组成:
- 如果需要,在更新阶段(Update Pass)更新约束
- 如果需要,在布局阶段(Layout Pass)重设视图的frame
更新阶段
系统会遍历视图层级,并在所有的ViewController上调用updateViewConstraints方法、在所有视图上调用updateConstraints方法。你可以覆盖这些方法来优化约束的修改(查看Batching Changes)。
布局阶段
系统再一次遍历视图层级,并在所有ViewController上调用viewWillLayoutSubviews、在所有视图上调用layoutSubViews。默认来说,layoutSubViews方法会使用Auto Layout引擎算出的矩形来更新每个子视图的frame。你可以覆盖这些方法来修改布局(查看Custom Layouts)。
分阶段修改(Batching Changes)
在有影响的修改发生后,直接更新约束应该总是最简洁、最方便的方式。将这些修改延迟到之后的方法中执行,会使代码更加复杂并难以理解。
可是,总有一些时候你想要为了优化性能而进行分阶段的修改。如当修改约束的地方执行太慢,或是视图正在进行许多冗余修改时,使用这种方式便可以解决。
要对改变进行分阶段执行,不要直接进行修改,而是在包含该约束的视图上调用setNeedsUpdateConstraints方法。之后,覆盖视图的updateConstraints方法来修改受影响的约束。
说明
你的updateConstraints的实现必须尽可能高效。不要让所有的约束失效(deactivate),可以根据需要失效其中一部分。此外,你的app必须存在某些方式来追踪约束,并且可以在每一个更新阶段来验证它们。只改变那些需要改变的项目。在每一次更新阶段中,你必须确保为app的当前状态提供了适合的约束。
在updateConstraints方法实现中,最后一步一定要调用父类的实现。
不要在你的updateConstraints方法中调用setNeedsUpdateConstraints方法。调用setNeedsUpdateConstraints会设置另一个更新阶段,进而生成了调用循环。
自定义布局(Custom Layouts)
覆盖viewWillLayoutSubViews或layoutSubViews方法来修改layout引擎返回的结果。
重点
如果可能,使用约束来定义所有的布局。这可以使布局结果更加健壮且更易调试。当你需要创建一个布局,且此布局仅仅通过约束无法达到要求时,你只能通过覆盖viewWillLayoutSubViews或layoutSubViews方法进行处理。
在覆盖这些方法时,布局正处在一个不确定的状态。一部分视图可能已经布局完,另一部分则没有。在修改视图层级时,你需要非常小心,否则很可能就会导致调用循环。如下规则可以帮助你避免调用循环:
- 你必须在方法的某处调用父类的实现。
- 你可以安全地在子树中让视图布局无效化;可是,你必须在调用父类的实现之前做这件事。
- 不要让在子树外的任何视图布局无效化。这会导致调用循环。
- 不要调用setNeedsUpdateConstraints。你刚刚完成了一次更新阶段。调用此方法会产生一个调用循环。
- 不要调用setNeedsLayout。调用此方法会产生一个调用循环。
- 修改约束时要格外小心。你不能意外地让子树外的视图布局无效化。