在获取 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 处的单元格时崩溃。这可能吗?
编辑:
用户交互是这样的:
- 用户点击 table 视图单元格内的按钮 [添加朋友] <-- indexPath 在此处检索
- 将按钮更改为[已添加]以显示已收到点击。 <-- 此处调用的 reloadData
- 在短暂延迟(0.5 秒)后淡出单元格。 <-- 从 #1
中使用 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
的值类型。但这些是与您的更大问题无关的实施细节。)
产生:
我正在尝试追踪应用中的严重崩溃。
我有一些代码可以有效地做到这一点:
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 处的单元格时崩溃。这可能吗?
编辑:
用户交互是这样的:
- 用户点击 table 视图单元格内的按钮 [添加朋友] <-- indexPath 在此处检索
- 将按钮更改为[已添加]以显示已收到点击。 <-- 此处调用的 reloadData
- 在短暂延迟(0.5 秒)后淡出单元格。 <-- 从 #1 中使用 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
的值类型。但这些是与您的更大问题无关的实施细节。)
产生: