【聚沙成塔】Swift - layoutSubviews、layoutIfNeeded、setNeedsLayout

4,439 阅读2分钟

对于 iOS 中的视图布局,正确的使用上面的三个方法是非常重要的,下面就来深入的讲解一下三个方法的作用以及不同之处。

在开始之前先解释一个词:更新周期(update cycle)。

所谓的更新周期,即更新当前屏幕视图的一个过程,它会在当前 run loop 结束时开始。

setNeedsLayout()

调用该方法:即告诉 App 在更新周期时,布局和重绘当前视图及其子视图。

该方法是异步操作且必须在主线程中调用,它调用完成会立即返回。因为你不知道更新周期什么时候会开始,所以,调用该方法不能精准的控制视图约束更新的时间。但是由于该方法可以将多个布局更新在一个更新周期中更新,因此它的性能会更好。

layoutIfNeeded()

调用该方法:即告诉 App 立即布局和重绘当前视图及其子视图,不需要等待更新周期。

该方法是同步操作,布局更新和视图重绘已在该方法调用完成之前全部更新。

layoutSubviews()

该方法会根据你设置的约束来确定子视图的位置和尺寸。

当子视图执行更加精细的布局时,可以重写该方法。只有当子视图的自动调整(autoresizing)和基于约束的行为不能提供所需的行为时,才应该重写此方法。

不要直接调用该方法,如果你想强制更新布局,可以调用 setNeedsLayout() 方法,如果想立刻更新布局,可以调用 layoutIfNeeded() 方法。

应用

// 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():异步、 在更新周期中更新布局,性能更佳。

参考