首页 > 解决方案 > 从 UITableView 插入/删除单元格后的 IndexPath 错误

问题描述

代码

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将涉及然后indexPath传入viewModel.updateSelectionState将是错误的(将等于单元格删除之前的值)。

我想我知道为什么

  1. IndexPath是一个结构,因此handleDidChangeSelectionState保留当前值(不是实例)的副本。原始值的任何更新都不会更新捕获的副本。
  2. tableView.deleteRows不会重新加载 tableview 的数据源,所以cellForRowAt不会召回。这意味着handleDidChangeSelectionState不会捕获更新的副本。

我解决这个问题的方法

* 第一个

询问里面的 indexPath 值handleDidChangeSelectionState

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)
}

* 第 2 名

每次删除后执行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()
    })

问题

哪种方法更好?

我想要:

也许有更好的第三种方法。

感谢您的任何建议。

标签: iosswiftuitableview

解决方案


只有一种方法满足保持流畅动画的第一个条件。在中断动画reloadData后立即调用。deleteRows

而且调用indexPath(for: cell)肯定比重新加载整个表视图更便宜。


推荐阅读