对于 iOS 中的视图布局,正确的使用上面的三个方法是非常重要的,下面就来深入的讲解一下三个方法的作用以及不同之处。
在开始之前先解释一个词:更新周期(update cycle)。
所谓的更新周期,即更新当前屏幕视图的一个过程,它会在当前 run loop 结束时开始。
setNeedsLayout()
调用该方法:即告诉 App 在更新周期时,布局和重绘当前视图及其子视图。
该方法是异步
操作且必须在主线程中调用,它调用完成会立即返回。因为你不知道更新周期什么时候会开始,所以,调用该方法不能精准的控制视图约束更新的时间。但是由于该方法可以将多个布局更新在一个更新周期中更新,因此它的性能会更好。
layoutIfNeeded()
调用该方法:即告诉 App 立即
布局和重绘当前视图及其子视图,不需要等待更新周期。
该方法是同步
操作,布局更新和视图重绘已在该方法调用完成之前全部更新。
layoutSubviews()
该方法会根据你设置的约束来确定子视图的位置和尺寸。
当子视图执行更加精细的布局时,可以重写该方法。只有当子视图的自动调整(autoresizing)和基于约束的行为不能提供所需的行为时,才应该重写此方法。
不要直接调用该方法,如果你想强制更新布局,可以调用 setNeedsLayout()
方法,如果想立刻更新布局,可以调用 layoutIfNeeded()
方法。
应用
- 具体详情可参见我的这篇文章 - iOS 如何动画改变约束
- 全文/收起 - 动态改变 UITableViewCell 高度
// 1. 在delegate 方法中调用 layoutIfNeeded() 方法
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 1.1 cell 的具体配置
xxxxx
// 1.2 调用 layoutIfNeeded()
cell.layoutIfNeeded()
return cell
}
// 2. 改变相应约束
xxConstraints.const = 100
// 3. 全文/收起 按钮的点击事件 reload 相应 cell
@objc private buttonAction() {
tableView.reloadRows(at: [xxIndexPath], with: .automatic)
}
约束改变的最佳实践
Apple Document: It is almost always cleaner and easier to update a constraint immediately after the affecting change has occurred. Deferring these changes to a later method makes the code more complex and harder to understand.
Apple 文档推荐我们在发生约束改变的情况时,立即修改约束是最简单明了的方式。比如在按钮点击事件中需要更新约束时的代码:
@IBAction func buttonAction(_ sender: UIButton) {
animationChange()
}
private func animationChange() {
// 在需要改变时,首先改变约束
heightConstraint.constant = 300
//在根据需求处理响应逻辑
UIView.animate(withDuration: 2.0) {
self.view.layoutIfNeeded()
}
}
总结
- layoutSubviews():不要直接调用,可以在子类中重写该方法以获得子视图更加精细的布局。
- layoutIfNeeded():同步、立即更新重绘当前视图及子视图,不等待更新周期。
- setNeedsLayout():异步、 在更新周期中更新布局,性能更佳。