日常收录

iOS 布局相关方法

2021-09-26  本文已影响0人  搬砖的crystal

一、布局

一个视图的布局指的是它在屏幕上的的大小和位置。每个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的位置需要被重新计算,继而会自动转化为一个最终的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,但是不同于layoutSubviewsdrawRect方法不会触发后续对视图的子视图方法的调用。同样,和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已经过期,需要在下一个布局阶段重新计算。

上一篇下一篇

猜你喜欢

热点阅读