autolayout布局流程

在AppKit和Foundation的frameworks中,布局是最基本的工作。我们在build应用程序时,AutoLayout是我们最常接触的,但有时它会让人迷惑,所以今天我想从Auto Layout的不容易理解的几个方面讲起。

The Layout Cycle

你可能知道如何配置用户界面了,但是Auto Layout仍然像一个黑盒一样,当你配置一些东西,运行你的工程,希望界面按照你想的布局,但是却不是这样。很难知道是哪出问题了,我们如何通过设置view的constraints来给view设置frames。现在我们预览一下这个过程


我们开启application runloop去循环的查询直到constraint的改变导致计算的布局发生改变,这就导致一个defferred layout pass将会执行。当layout pass 真正执行了,我们会更新views的frames(we go through the hierarchy,and update all the
frames for the views)。
这有一点点抽象,所以我做了一个例子。

当我们反选上面的checkbox时,我们将修改一个constraint来缩小这个window,并且隐藏下面的checkboxes。当我们改变constraint时,layout engine内部已经改变了,但是界面还没有更新。当layout pass执行时,界面才最终按照engine的方式更新。

constraints changes

创建的约束最终会变成数学表达式放在layout engines里。所以约束的任何改变都会影响这些表达式,包括很明显的改变比如activating constraints、deactivating constraints、改变 priority和改变constant,还有一些不明显的,比如改变视图层级,因为这会间接的导致constraints的改变。

那么,当约束改变的时候,真正发生了什么呢?首先layout engine 会重新计算布局。那些表达式是由变量组成,这些变量描述了view的大小位置。当重新计算布局的时候,这些变量的值会更新,然后通知变量所描述的view,将它标记为 needing layout。整个过程就导致了 deferred layout pass 将会执行。

defferred layout pass

在我们的例子中,frame实际上在layout engine中改变了,但并没有在界面中改变,那么当 deferred layout pass 执行了,它的目的就是重新布局所有的位置不正确的views,所以完成以后,一切都正确布局了。

psss这个词 实际上有点用词不当。实际上有几个过程。首先就是updating constraints。updating constraints是为了确保是否有将要发生的constraints的改变。这些改变要在我们改变界面层次或者重新布局views之前。第二个过程是重新布局views了。

views需要明确的要求它们的update constraints方法被调用。这个方法做的事情与setNeedsDisplay差不多。当你调用setNeedsUpdateConstraints,一会update constraints方法就会调用。这些方法都试味了让views的constrains的改变能及时的赶上下一个 layout pass,但它并不是很常用。你所有的约束正常应该在 Interface Builder中创建,或者在代码中创建,在代码中创建时,放在viewDidLoad里会更好。Update constraints是周期性的重复工作的,所以当你需要改变constraints时,直接改就可以了。

如果你发现直接改constraints太慢了,那么update constraints会帮助你。在update constraints里改constraints会在比别的地方更快。原因是layout engine 能够对这个过程中的改变进行批处理,得益于批处理,对view的整个constraints数组进行批量改变也可以很快。

一个常见的模式就是为了不同的配置重新建立不同的constraints,用setNeedsUpdateConstraints就简单而高效。

在任何情况下,一旦这个过程(updating constraints)完成了,我们就可以认为constrains就是最新的了,我们就准备好了开始重新布局views了。这就是我们改变视图层次的地方,我们将对标记为eeding layout的views调用layoutSubviews,在OS X系统中,这个方法叫做layout,意思一样。目的是让接收者重新布局它的子视图,而不是它自己。系统会将子视图的frames从layout engine读取出来并负值给子视图。在MAC OS X调用setFrame,在iOS调用setBounds和setCenter,意思是一样的。 这就是界面真正更新的时候。


第二个过程就是layoutSubviews,很多人为了某种自定义的布局会复写这个方法,如果你真的需要的话就这么做,但是你需要知道这会很容易引起麻烦。所以为我想详细的说说这个问题。

只有在 为了实现某种constraints无法表达的布局 时,才复写layoutSubviews方法。如果你能用constraints实现,即使复杂粗暴,也会少麻烦。

如果你真的选择复写这个方法,那么你要记住,我们处于layout的中间过程,有些views已经布局好了,有些还没有,可能马上就布局好了。这是一个微妙的时刻。

有些特殊的规则需要遵守。

  • 你需要调用super layoutSubviews。这是为了各种目的。你还可以取消(invalidate)子视图的所有的布局,但是必须在super layoutSubviews之前。
  • 不要调用setNeedsUpdateConstraints。因为我们已经完成了update constraints过程。如果我们真的需要调用,已经晚了。
  • 不要取消(invalidate)view子视图以外的视图的布局。如果你做了,很容易引起反馈循环(feedback loops)。并且导致布局过程出问题。所以我们应该不要引发这种循环反应。

你会经常发现为了让你的视图放在正确的位置你需要在你复写的layoutSubviews里尼修改constraints,这样是可以的,但是你要小心。很难预测当你修改一个constraint时,别的地方的视图会不会受影响。如果你改变了constraints,很容易导致子视图以外的视图的布局失效(invalidate)。

the layout cycle

在任何情况下,假设所有过程都顺利进行,layout cycle 会在这个点完成,所有的视图都在正确的位置,我们修改的constraints已经起完全起作用了。关于layout cycle 需要注意几点:

  • 当你修改constraints时,不要期望view的frame会立即改变。我们刚刚说了都会发生哪些过程。
  • 如果复写了layoutSubviews,注意不要引起反馈循环(feedback loops),因为这很难调适

How View Controller Participate in the View Layout Process