从 UITableView 插入/删除单元格后的 IndexPath 错误
Posted
技术标签:
【中文标题】从 UITableView 插入/删除单元格后的 IndexPath 错误【英文标题】:Wrong IndexPath after insertion/deletion of cell from UITableView 【发布时间】:2019-02-27 08:49:33 【问题描述】:代码
VC问题部分的要点:
// Part of VC where cell is setting
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCell(for: indexPath) as Cell
let cellVM = viewModel.cellVM(for: indexPath)
cell.update(with: cellVM)
cell.handleDidChangeSelectionState = [weak self] selected in
guard
let `self` = self
else return
self.viewModel.updateSelectionState(selected: selected, at: indexPath)
return cell
// Part of code where cell can be deleted
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?
let deleteAction = UITableViewRowAction(style: .destructive, title: "delete".localized, handler: [weak self] _, indexPath in
guard let self = self else return
self.viewModel.delete(at: indexPath)
tableView.deleteRows(at: [indexPath], with: .left)
)
return [deleteAction]
问题
当单元格被删除后,handleDidChangeSelectionState
将参与其中,那么传递给 viewModel.updateSelectionState
的 indexPath
将是错误的(将等于删除单元格之前的值)。
我想我知道为什么
IndexPath
是一个结构,所以 handleDidChangeSelectionState
保留当前值的副本(不是实例)。原始值的任何更新都不会更新捕获的副本。
tableView.deleteRows
不会重新加载 tableview 的数据源,所以 cellForRowAt
不会召回。这意味着handleDidChangeSelectionState
不会捕获更新的副本。
解决这个问题的方法
* 第一
询问handleDidChangeSelectionState
里面的indexPath值:
cell.handleDidChangeSelectionState = [weak self, weak cell] selected in
guard
let `self` = self,
let cell = cell,
// now I have a correct value
let indexPath = tableView.indexPath(for: cell)
else return
self.viewModel.updateSelectionState(selected: selected, at: indexPath)
* 第二次
每次删除后执行reloadData()
:
let deleteAction = UITableViewRowAction(style: .destructive, title: "delete".localized, handler: [weak self] _, indexPath in
guard let self = self else return
self.viewModel.delete(at: indexPath)
tableView.deleteRows(at: [indexPath], with: .left)
// force to recall `cellForRowAt` then `handleDidChangeSelectionState` will capture correct value
tableView.reloadData()
)
问题
哪种方法更好?
我想:
保持流畅的动画效果(感谢tableView.deleteRows(at: []
)
找到更好的性能(我不确定哪个更好,reloadData()
或 indexPath(for cell:)
)
也许有更好的第三种方法。
感谢您的任何建议。
【问题讨论】:
【参考方案1】:只有第一种方法满足保持流畅动画的第一个条件。在deleteRows
之后立即调用reloadData
会中断动画。
而且调用indexPath(for: cell)
肯定比重新加载整个表格视图要便宜。
【讨论】:
【参考方案2】:不要使用重新加载特定行,因为删除行时索引已更改,并且删除行后会删除动画,这就是您需要重新加载的原因tableview.reloadData()
在您的情况下这是一个更好的选择。
let deleteAction = UITableViewRowAction(style: .destructive, title: "delete".localized, handler: [weak self] _, indexPath in
guard let self = self else return
self.viewModel.delete(at: indexPath)
tableView.deleteRows(at: [indexPath], with: .left)
// force to recall `cellForRowAt` then `handleDidChangeSelectionState` will capture correct value
tableView.reloadData()
)
【讨论】:
以上是关于从 UITableView 插入/删除单元格后的 IndexPath 错误的主要内容,如果未能解决你的问题,请参考以下文章
自定义 UITableView 单元格 Nib 文件仅在选择单元格后显示?