布局
layoutSubviews
不能显示调用这个方法,有许多可以在run loop 的不同时间点触发layoutSubviews的调用机制,这些触发机制比直接调用layoutSbuviews的资源消耗要小的多
可以自动调用layoutSubviews的动作有:
- 修改view的大小
- 新增子view
- 用户在UIScrollView上滚动(layoutSubviews会在UIScrollView和它的父view上被调用)
- 用户旋转设备
- 跟新视图的constraints
这些方式都会告知系统view的位置需要被重新计算,继而会自动转化为一个最终的layoutSubviews调用。当然,也有直接触发layoutSubviews的方法。
setNeedsLaylout()
触发layoutSubviews调用的最省资源的方法就是在你的视图上调用setNeedsLaylout方法。调用这个方法代表向系统表示视图的布局需要重新计算。setNeedsLaylout方法会立即执行并返回,但在返回前不会真正更新视图。视图会在下一个update cycle中更新,就在系统调用视图们的layoutSubviews以及他们的所有子视图的layoutSubviews方法的时候。即使从setNEedsLayout返回到视图被重新绘制并布局之前有一段任意时间间隔,但是这个延迟不会对用户造成影响,因为永远不会长到对界面造成卡顿。
layoutifNeeded()
layoutifNeeded是另一个会让UIView触发layoutSubviews的方法。当视图需要更新的时候,与setNeeddsLayout()会让视图在下一周期调用layoutSubviews更新视图不同,layoutIfNeeded会立即调用layoutSubviews方法。但是如果你调用了layoutIfNeeded之后,并且没有任何操作向系统表明需要刷新视图,那么就不会调用layoutsubview。如果你在同一个run loop内调用了两次layoutIfNeeded,并且两次之间没有更新视图,第二个调用同样不会触发layoutSubviews方法。
使用layoutIFfNeeded,则布局和重绘会立即发生并在函数返回之前完成(除非有正在运行中的动画)。这个方法在你需要依赖新布局,无法等到下一次update cycle的时候会比setNeedsLayout有用。除非是这种情况,否则你更应该使用setNeedsLayout,这样在每次run loop中都只会更新一次布局。
当对希望通过修改constraint进行动画时,这个方法特别有用。你需要在animation block之前调用layoutIfNeeded,以确保在动画开始之前传播所有的布局更新,在animation block 中设置新constrait后,需要在此调用layoutIfNeeded来动画到新的状态。
后
当layoutSubviews完成后,在view的所有者view controller上会触发viewDidLayoutSubviews调用,因为viewDidLayoutSubviews是view布局更新后会被唯一可靠调用的方法,应该把所有依赖布局或者大小的代码放在viewDidLayoutSubviews中。这是避免使用过时的布局或者位置变量的唯一方法。
显示
一个视图的显示包含了颜色、文本、图片和Core Graphics回执等视图属性,不包括其本身和子视图的大小和位置。和布局的方法类似,显示也有触发更新的方法,它们由系统在检测到更新时被自动调用,或者我们可以手动调用直接刷新。
draw(_:)
UIView的draw方法(Objective-C的drawRect)方法对视图内容显示的操作,类似于视图布局的layoutSubviews,但是不同于layoutSubviews,draw方法不会触发后续对视图的子视图方法的调用。同样,和layoutSubviews一样,你不应该直接调用draw方法,而应该通过调用触发方法,让系统在run loop中的不同结点自动调用。
setNeedsDisplay()
这个方法类似于布局中的setNeedLayout.它会给有内容更新的视图设置一个内部标记,但在视图重绘之前就会返回。然后在下一个update cycle中,系统会遍历所有已标记的视图,并调用它们的draw方法。如果你只想在下次更新时重绘部分视图,你可以调用setNeedsDicplay(_:),并把需要重绘的矩形部分传进去(setNeedsDisplayInRect in OC)。大部分时候,在视图中跟新任何UI组建都会把相应的视图标记为“dirty”,通过设置视图“内部更新标记”,在下一次update cycle 中就会重绘,而不需要显示的setNeedsDisplay调用。然而如果你有一个属性没有绑定到UI组建,但需要在每次更新时重绘视图,你可以定义他的didSet属性,并且调用setNeedsDisplay来触发视图合适的更新。
有时候设置一个属性要求自定义绘制,这种情况下你需要重写draw方法。
视图的显示方法里没有类似布局中的layoutIfNeeded这样可以触发立即更新的方法。通常情况下等到下一个更新周期在重新绘制视图也无所谓。
约束
自动布局包含三步来布局和重绘视图。第一步是更新约束,系统会计算并给视图设置所有要求的约束。第二步是布局阶段,布局引擎计算视图和子视图的 frame 并且将它们布局。最后一步完成这一循环的是显示阶段,重绘视图的内容,如实现了 draw 方法则调用 draw。
updateConstraints()
这个方法用来在自动布局中动态改变视图约束。和布局中的layoutSubviews()方法或显示中的draw方法类似。updateConstraints() 只应该被重载,绝不要再代码中显示地调用。通常你只应该在updateConstraints方法中实现必须要更新的约束。静态的约束应该在interface builder、视图的初始化方法或者viewDidLoad()方法中指定。
通常情况下,开启或者关闭constrains、更改constrain的属性或者常量值、或者从视图层级中转移一个视图是都会设置一个内部的标记,这个标记会在下一个更新周期中触发调用updateConstrains。当然,也有手动给视图打上“update constarints”标记的方法,如下。
setNeedsUpdateConstraints()
调用 setNeedsUpdateConstraints() 会保证在下一次更新周期中更新约束。它通过标记“ constrant 已 update”来触发 updateConstraints()。这个方法和 setNeedsDisplay() 以及 setNeedsLayout() 方法的工作机制类似。
updateConstraintsIfNeeded()
对于使用自动布局的视图来说,这个方法与 layoutIfNeeded 等价。它会检查“更新 constrains”标记(可以被 setNeedsUpdateConstraints 自动设置,或者 invalidateInstrinsicContentSize)。如果它认为这些约束需要被更新,它会立即触发 updateConstraints() ,而不会等到 run loop 的末尾。
invalidateIntrinsicContentSize()
自动布局中某些视图拥有 intrinsicContentSize 属性,这是视图根据它的内容得到的自然尺寸。一个视图的 intrinsicContentSize 通常由所包含的元素的约束决定,但也可以通过重载提供自定义行为。调用 invalidateIntrinsicContentSize() 会设置一个标记表示这个视图的 intrinsicContentSize 已经过期,需要在下一个布局阶段重新计算。
串联起来
布局、显示和约束都遵循着相似的模式,例如他们更新的方式以及如何在 run loop 的不同时间点上强制更新。任意组件都有一个实际去更新的方法(layoutSubviews, draw, 和 updateConstraints),你可以重写来手动操作视图,但是任何情况下都不要显式调用。这个方法只在run loop的末端会被调用,如果视图被标记了告诉系统该视图需要被更新的标记的话。有一些操作会自动设置这个标志,但是也有一些方法允许您显式地设置它。对于布局和约束相关的更新,如果你等不到在run loop末端才更新(例如:其他行为依赖于新布局),有方法可以让你立即更新,并保证“布局需更新”标记被正确标记。下面的表格列出了任意组件会怎样更新及其对应方法。