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