【问题标题】:Wrong IndexPath after insertion/deletion of cell from UITableView从 UITableView 插入/删除单元格后的 IndexPath 错误
【发布时间】: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 将是错误的(将等于删除单元格之前的值)。

我想我知道为什么

  1. IndexPath 是一个结构,所以 handleDidChangeSelectionState 保留当前值的副本(不是实例)。原始值的任何更新都不会更新捕获的副本。
  2. 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:)

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

感谢您的任何建议。

【问题讨论】:

    标签: ios swift uitableview


    【解决方案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()
          })
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多