我在 UITableViewCell 内部有一个 UIProgressBar,它根据倒计时器每秒更新一次,该计时器也在单元格对象内部运行。它在大多数情况下都工作得很好,直到应用程序进入后台或另一个模态视图覆盖它 - 不知何故,普通视图不会触发此操作。
应用程序从此状态返回后,进度条将停止监听计时器,并卡在应用程序中其他任何地方未引用的相同随机值(大约 10%)上。奇怪的是,当我在计时器中修改进度条后打印进度条的当前进度时,它会正确返回,尽管 UI 不尊重它。
以下是该问题的记录:https://imgur.com/a/oZ9AGUh
这是相关代码:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
DispatchQueue.main.async {
(cell as! CountdownTable).timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in
let nowToEvent = (cell as! CountdownTable).dateTarget - Date()
let thenToEvent = (cell as! CountdownTable).dateTarget - (cell as! CountdownTable).dateWhenSet
let elapsed = thenToEvent - nowToEvent
(cell as! CountdownTable).countdownBar.setProgress(Float(elapsed / thenToEvent), animated: true)
(cell as! CountdownTable).countdownDate.text = (cell as! CountdownTable).dateTarget.timeFromDate(from: Date())
})
}
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
(cell as! CountdownTable).timer.invalidate()
}
此代码解决了应用程序后台运行或模态视图出现后进度条不更新的问题,方法是对整个表格视图使用单个
Timer
,而不是为每个单元格使用计时器。
解决方案要点如下:
Timer
实例现在是管理表视图的UIViewController
的一部分,而不是单独的UITableViewCell
的一部分。当视图控制器的视图出现 (viewDidAppear(_:)
) 时,此计时器启动,当视图消失 (viewWillDisappear(_:)
) 时,此计时器失效。
计时器每 0.1 秒触发一次并调用
updateVisibleCells
函数。选择 0.1 秒的间隔是为了提供更平滑的 UI 更新。您可以根据您的需求和性能考虑调整此值。
updateVisibleCells
函数迭代表视图中所有当前可见的单元格(通过调用tableView.indexPathsForVisibleRows
获得)。它计算经过的时间并更新每个可见单元格的进度条的进度。
此方法可确保当应用程序从后台返回或模态视图关闭时进度条继续更新,因为计时器在视图控制器可见的时间内继续运行,而不管单元格级别发生任何变化。
通过不在每个表格单元中使用计时器,此方法还可以提高性能和电池效率,因为无论可见单元的数量如何,都只使用单个计时器。当表格视图有许多单元格时,这尤其有用。
这是代码的重构版本,考虑了@Rob 和@Paulw11 讨论的建议。请注意,此代码假设存在一个存储
dateWhenSet
和 dateTarget
值的模型,以及计算 timeFromDate(from:)
的方法。
class CountdownTable: UITableViewCell {
var timer: Timer?
var dateWhenSet: Date?
var dateTarget: Date?
var countdownBar: UIProgressView!
var countdownDate: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// setup cell
}
}
class YourViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var timer: Timer?
var tableView: UITableView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateVisibleCells), userInfo: nil, repeats: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
timer?.invalidate()
}
@objc func updateVisibleCells() {
guard let visibleRows = tableView.indexPathsForVisibleRows else { return }
for indexPath in visibleRows {
guard let cell = tableView.cellForRow(at: indexPath) as? CountdownTable else { continue }
let nowToEvent = cell.dateTarget! - Date()
let thenToEvent = cell.dateTarget! - cell.dateWhenSet!
let elapsed = thenToEvent - nowToEvent
cell.countdownBar.setProgress(Float(elapsed / thenToEvent), animated: true)
cell.countdownDate.text = cell.dateTarget?.timeFromDate(from: Date())
}
}
}
此代码在
viewDidAppear(_:)
中设置一个计时器,每 0.1 秒触发一次,以更新当前可见单元格的进度条。计时器在viewWillDisappear(_:)
中失效,以确保当视图控制器不再可见时计时器停止。这是比为每个单元创建和管理单独的计时器更有效的方法。
此解决方案假设
UITableViewCell
子类具有属性 countdownBar
、countdownDate
、dateTarget
和 dateWhenSet
。确保这些属性存在并且配置正确。
该解决方案在原始问题中概述的所有情况下都保持进度条和倒计时器的更新。通过将计时器移至视图控制器并仅迭代可见单元格,它还提供了更高效、更平滑的 UI 更新。