从 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.updateSelectionStateindexPath 将是错误的(将等于删除单元格之前的值)。

我想我知道为什么

    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 文件仅在选择单元格后显示?

UICollectionView:添加单元格后内容偏移错误

UITableView 中的自定义绘图在 5 个单元格后失败

删除单元格后刷新tableView

无法识别单击单元格后的 tableView segue