在获取 indexPath 之后但在删除该 indexPath 处的单元格之前调用 reloadData() 是否不安全?

Is it unsafe to call reloadData() after getting an indexPath but before removing a cell at that indexPath?

我正在尝试追踪应用中的严重崩溃。

我有一些代码可以有效地做到这一点:

if let indexPath = self.tableView.indexPath(for: myTableViewCell) {
    // .. update some state to show a different view in the cell ..
    self.tableView.reloadData()

    // show nice fade out of the cell
    self.friendRequests.remove(at: indexPath.row)
    self.tableView.deleteRows(at: [indexPath], with: .fade)
}

问题是调用 reloadData() 以某种方式使我刚刚检索到的 indexPath 无效,因此应用程序在尝试删除该 indexPath 处的单元格时崩溃。这可能吗?

编辑:

用户交互是这样的:

  1. 用户点击 table 视图单元格内的按钮 [添加朋友] <-- indexPath 在此处检索
  2. 将按钮更改为[已添加]以显示已收到点击。 <-- 此处调用的 reloadData
  3. 在短暂延迟(0.5 秒)后淡出单元格。 <-- 从 #1
  4. 中使用 indexPath 调用此处删除

我可以将我的代码更改为不调用 reloadData 而只是更新单元格的视图。这是可取的吗?如果我不这样做会发生什么?

是的,可能发生的情况是 table 视图使存储的索引路径无效。

要测试它是否是问题,请尝试在调用 reloadData() 之前更改 table 中表示的数据。

如果这是一个问题,那么您需要使用 table 单元格表示的对象的标识符而不是索引路径。修改后的代码将如下所示:

func objectIdentifer(at: IndexPath) -> Identifier? {
   ...
}

func indexPathForObject(_ identifier: Identifier) -> IndexPath? {
   ...
}

if 
    let path = self.tableView.indexPath(for: myTableViewCell) 
    let identifier = objectIdentifier(at: path) {

    ...

    self.tableView.reloadData()

    ...

    if let newPath = indexPathForObject(identifier) {
        self.friendRequests.removeObject(identifier)
        self.tableView.deleteRows(at: [newPath], with: .fade)
    }
}

就个人而言,我只是用 reloadRows(at:with:) 重新加载有问题的按钮,而不是整个 table 视图。这不仅效率更高,而且如果您还没有滚动到列表顶部,它还可以避免滚动列表时出现不和谐的情况。

然后我会将 deleteRows(at:with:) 动画延迟一小段时间。我个人认为 0.5 秒太长了,因为用户可能会继续点击另一行,如果他们不幸在动画期间的错误时间点击,他们很容易得到与预期不同的行。您希望延迟时间足够长,以便他们对所点击的内容进行肯定确认,但时间又不足以产生令人困惑的用户体验。

无论如何,你最终会得到这样的结果:

func didTapAddButton(in cell: FriendCell) {
    guard let indexPath = tableView.indexPath(for: cell), friendsToAdd[indexPath.row].state == .notAdded else {
        return
    }

    // change the button

    friendsToAdd[indexPath.row].state = .added
    tableView.reloadRows(at: [indexPath], with: .none)

    // save reference to friend that you added

    let addedFriend = friendsToAdd[indexPath.row]

    // later, animate the removal of that row

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
        if let row = self.friendsToAdd.index(where: { [=10=] === addedFriend }) {
            self.friendsToAdd.remove(at: row)
            self.tableView.deleteRows(at: [IndexPath(row: row, section: 0)], with: .fade)
        }
    }
}

(注意,我使用 === 是因为我使用的是引用类型。如果处理值类型,我会使用 == 和符合 Equatable 的值类型。但这些是与您的更大问题无关的实施细节。)

产生: