UIView在AutoLayout下的布局过程

巢海
2023-12-01

使用 Auto Layout 将 View 显示在屏幕上需要的经过三个步骤。

  1. 更新约束(updateConstraints)
  2. 通过约束关系计算出center和bounds对subviews进行布局(layoutSubViews)
  3. 将布局好的view显示到屏幕(drawRect)

与之相关的方法有如下八个。

  1. setNeedsUpdateConstraints:将 view 标记为需要更新约束,并在稍后触发updateConstraitsIfNeed
  2. updateConstraitsIfNeed:系统会在每个布局节点自动调用此方法。只有约束被标记为需要更新才会调用updateConstraints。此方法可以手动调用。子类不要重写此方法。
  3. updateConstraints:更新约束的实际方法。
  4. setNeedsLayout:将 view 标记为需要更新布局,并在稍后触发layoutIfNeeded。当view的布局改变时会自动调用。
  5. layoutIfNeeded:系统会在每个布局节点自动调用此方法。只有布局被标记为需要更新才会调用layoutSubViews
  6. layoutSubViews:在 iOS5.1 之后的是更新布局的实际方法,之前没有默认实现。只有当 autoresizing 和 constraint 不能满足布局需求时才能重写。不能直接调用。会调用updateConstraintsIfNeeded
  7. setNeedsDisplay:将 view 标记为需要重绘,并在下次绘制循环触发 drawRect。改变布局不会触发此方法。
  8. drawRect:没有默认实现。不能直接调用。

详细

当你使用 Auto Layout 去努力创造你想要的布局时,特别是同时伴随高级使用场景和动画时,放下使用场景,去回想布局过程是怎样工作的,将会带来很大的帮助。
相比使用frame布局,Auto Layout 在 Views 可以被显示之前引入了两个额外的处理工序:更新约束,布局视图 
每一步都依赖前一步;显示依赖布局,布局依赖约束。

第一步:更新约束(updateConstraints)

更新约束可以被认为是一个测量的过程。他从下到上 bottom-up (from subview to super view)去准备在布局时直接设置 views 的 frame 所需要的信息。
你可用setNeedsUpdateConstraints手动触发这个过程。也可以通过对自身约束系统的改变来自动触发这个过程。即便这样,当你在定制 views 中改变可能影响布局的的约束时通知 Auto Layout 也是有必要的。
说到定制的 views,你可以重写updateConstraints以添加你的 view 在这个阶段需要的局部约束。

第二步:布局(Layout)

布局是从上到下 top-down (from super view to subview)进行的。布局的过程事实上是请求约束系统的结果再通过 center 和 bounds 设置给 views。
你可以通过调用setNeedsLayout触发这个过程。setNeedsLayout 实际上并没有在接下来立马请求布局,而是把你的请求记录下来随后刷新。
通过这种方法你不用担心setNeedsLayout调用太频繁,因为所有的布局请求会被合并到一个布局过程。你可以通过调用layoutIfNeeded来立即触发更新视图层的布局过程,如果你下一步操作依赖于视图最新的布局。
在你的定制 view 中你可以通过重写layoutSubviews去获取布局过程的完全控制。我们将在随后展示这样的用法。

第三部:显示(Display)

最后,显示过程将 views 渲染到屏幕,这个过程是独立的,不管你用没用 Auto Layout。
显示过程从上到下 top-down (from super view to subview) 进行,可以用 setNeedsDisplay 触发,并且会整合所有请求延时重绘。在你的定制 views 重写熟悉的drawRect:方法是你获得 views 在显示过程这个阶段的完全控制的途径。
因为每一步都依赖于前一步,所以显示过程将会触发布局过程(如果有任何布局在等待改变)。同样,布局过程将会触发新更新约束过程(如果约束系统有在等到的改变)。
需要记住的是,这三个过程的调用顺序并不是唯一的。
布局(基于约束 Constraint-based 的)是一个重复的过程。在布局过程中基于上一次布局结果改变约束,将会在下一个布局过程后再次触发约束更新。
这将有助于生成 views 高级定制的布局。但是你也有陷入无限循环的风险,如果每个layoutSubviews方法中调用并唤起了另外一个布局过程的话。

总结

  • 如果想要立即改变约束,需要在setNeedsUpdateConstraints后调用updateConstraintsIfNeeded
  • 如果想要立即改变布局,需要在setNeedsLayout后调用layoutIfNeeded




UIConstraintBasedLayoutCoreMethods

  1. @interface UIView (UIConstraintBasedLayoutCoreMethods)
  2. - ( void)updateConstraintsIfNeeded NS_AVAILABLE_IOS( 6_0);
  3. - ( void)updateConstraints NS_AVAILABLE_IOS( 6_0);
  4. - ( BOOL)needsUpdateConstraints NS_AVAILABLE_IOS( 6_0);
  5. - ( void)setNeedsUpdateConstraints NS_AVAILABLE_IOS( 6_0);
  6. @end

setNeedsUpdateConstraints : 当一个自定义的View某一个属性的改变可能影响到界面布局,我们应该调用这个方法来告诉布局系统在未来某个时刻需要更新。系统会调用updateConstraints去更新布局。

updateConstraints :自定义View时,我们应该重写这个方法来设置当前view局部的布局约束。重写这个方法时,一定要调用[super updateConstraints]

needsUpdateConstraints :布局系统使用这个返回值来确定是否调用updateConstraints

updateConstraintsIfNeeded :我们可以调用这个方法触发update Constraints的操作。在needsUpdateConstraints返回YES时,才能成功触发update Constraints的操作。我们不应该重写这个方法。

Auto Layout的布局过程是 update constraints(updateConstraints)-> layout Subviews(layoutSubViews)-> display(drawRect) 这三步不是单向的,如果layout的过程中改变了constrait, 就会触发update constraints,进行新的一轮迭代。我们在实际代码中,应该避免在此造成死循环。


 类似资料: