setNeedsLayout 和 layoutIfNeeded
在讨论 setNeedsLayout 和 layoutIfNeeded 之前,我们需要普及下主运行循环(run loop) 。
主运行循环
在应用启动时UIApplication会启动主运行循环,且主运行循环是在应用的主线程中运行。主运行循环处理用户相关的事件和基于视图的界面更新,并且所有用户事件会按照它们被接收时的顺序串行的执行。
当用户和应用交互时,和这些交互相关的事件由系统自动产生并且借助UIKit设定的特殊端口传递给应用。事件在应用内部以队列的形式存在并且逐个的被分发到应用的主运行循环去执行。UIApplication是第一个接收事件的对象,并且决定需要如何处理事件,如触控事件通常被分发到应用的主窗口对象,并且最终分发到发生该触控事件的视图上面。
下图展示了主运行循环的结构以及用户事件处理。
有时候事件当处理后需要更新视图,而视图不会马上更新,事实上是系统会标记哪些视图需要重新绘制,等到更新周期时才会重新渲染视图。
有的人可能会疑问为什么用户大都感觉不到视图在等待被重新绘制?因为这一切都发生的太快了。 请记住在事件处理中间存在一个更新周期,在该周期内系统会更新布局和重新渲染。明白了这个才能更好理解 setNeedsLayout 和 layoutIfNeeded的区别。
视图调用方法setNeedsLayout 是想告诉系统当前视图以及所有子视图需要在更新周期重新布局和重画,对于视图操作一个异步行为,因为这个方法会马上完成并返回,而不会等待布局更新和重画完成后再返回,至于更新周期发生时间也是未知的。
而方法 layoutIfNeeded 是同步调用,方法会告知系统需要布局和重绘当前视图和它的子视图,并且需要马上完成而不是等待下个更新周期。所以当这个方法返回的时候,布局已经调整结束,且根据以前的设置完成视图重绘。
简单来说, layoutIfNeeded 是马上更新视图, 而 setNeedsLayout 请在下个更新周期中更新视图。
自动布局和更新周期
在自动布局的概念中,关于布局和绘制视图共分3个阶段:
- 约束处理: 自下向上更新约束。
- 布局处理:根据阶段1中的约束,自上向下完成视图和子视图的布局。
- 显示处理:根据阶段2中的布局结果绘制视图。
实例
下面结合代码来在看一下 setNeedsLayout 和 layoutIfNeeded 区别。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var buttonHeight: NSLayoutConstraint!
@IBAction func heightPressed(sender: AnyObject) {
view.layoutIfNeeded()
if(self.buttonHeight.constant == 25.0) {
self.buttonHeight.constant = self.view.bounds.height - 200.0
} else {
self.buttonHeight.constant = 25.0
}
UIView.animateWithDuration(2.0) {
self.view.layoutIfNeeded()
}
}
}
上述代码片段中,修改约束,然后在时长2秒的动画闭包中强制刷新视图。
layoutIfNeeded() 会强制马上更新布局和显示,由于该方法是同步的,视图框架的移动会被动画模块捕获,如果运行上述代码片段,能看到按钮视图的高度变化动画。
然后把方法 layoutIfNeeded 替换为 setNeedsLayout, 更新后的动画模块如下:
UIView.animateWithDuration(2.0) {
self.view.setNeedsLayout()
}
方法setNeedsLayout只是把当前视图标记为需要更新布局,但不需要马上进行,而是等待在下一个更新周期中完成即可,这种异步行为导致动画模块捕获不到任何视图变化。
如果运行修改后的代码片段,可以发现按钮的高度会马上根据约束而改变,而不会看到修改前的变化动画。
需要注意的是,动画其实是在单独的线程中进行,而不是在更新周期中的主线程中进行。另外动画是在下个更新周期中触发的,动画进行期间,更新进度是在主线程发出,这是我们能看到递增变化的原因。
推荐阅读
iOS开发者注意, ATS 出没!
更多
获取更多内容请关注微信公众号豆志昂扬:
- 直接添加公众号豆志昂扬;
- 微信扫描下图二维码;