要知道表格视图何时完成加载其内容,我们首先需要对视图在屏幕上的显示方式有一个基本的了解。
在应用的生命周期中,有 4 个关键时刻:
- 应用收到事件(触摸、定时器、块调度等)
- 应用处理事件(它修改约束、启动动画、更改背景等)
- 应用计算新的视图层次结构
- 应用呈现视图层次结构并显示它
第 2 次和第 3 次是完全分开的。 为什么?出于性能原因,我们不想在每次修改完成时都执行所有计算(在 3 完成)。
所以,我认为您正面临这样的情况:
tableView.reloadData()
tableView.visibleCells.count // wrong count oO
这里出了什么问题?
表格视图会延迟重新加载其内容。实际上,如果你多次调用reloadData 不会产生性能问题。表格视图仅根据其委托实现重新计算其内容大小,并等待时刻 3 加载其单元格。这一次称为布局传递。
好的,如何参与布局关卡?
在布局过程中,应用计算视图层次结构的所有帧。要参与其中,您可以覆盖 UIView 子类中的专用方法 layoutSubviews、updateLayoutConstraints 等以及视图控制器子类中的等效方法。
这正是表格视图的作用。它覆盖 layoutSubviews 并根据您的委托实现添加或删除单元格。它在添加和布局新单元之前调用cellForRow,之后调用willDisplay。如果您调用了reloadData 或只是将表格视图添加到层次结构中,表格视图会根据需要添加尽可能多的单元格以在此关键时刻填充其框架。
好的,但是现在,如何知道表格视图何时完成重新加载其内容?
我们可以重新表述这个问题:如何知道表格视图何时完成了其子视图的布局?
• 最简单的方法是进入表格视图的布局:
class MyTableView: UITableView {
func layoutSubviews() {
super.layoutSubviews()
// the displayed cells are loaded
}
}
注意,这个方法在表格视图的生命周期中会被多次调用。由于表格视图的滚动和出列行为,单元格经常被修改、删除和添加。但它可以在super.layoutSubviews() 之后立即加载单元格。这个解决方案相当于等待最后一个索引路径的willDisplay事件。添加单元格时,在表格视图的layoutSubviews 执行期间调用此事件。
• 另一种方法是在应用完成布局传递时收到通知。
如documentation 中所述,您可以使用UIView.animate(withDuration:completion) 的选项:
tableView.reloadData()
UIView.animate(withDuration: 0) {
// layout done
}
此解决方案有效,但屏幕会在布局完成和块执行之间刷新一次。这等效于 DispatchMain.async 解决方案,但已指定。
• 或者,我更愿意强制表格视图的布局
有一种专用方法可以强制任何视图立即计算其子视图帧layoutIfNeeded:
tableView.reloadData()
table.layoutIfNeeded()
// layout done
但是请注意,这样做会删除系统使用的延迟加载。重复调用这些方法可能会产生性能问题。确保在计算表格视图的框架之前不会调用它们,否则表格视图将被再次加载并且不会通知您。
我认为没有完美的解决方案。子类化可能会导致麻烦。布局通道从顶部开始到底部,因此当所有布局完成时不容易收到通知。而layoutIfNeeded() 可能会造成性能问题等。