Reputation: 10752
I'm trying to track down a difficult crash in an app.
I have some code which effectively does this:
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)
}
The concern is that calling reloadData() somehow makes the indexPath I just retrieved invalid so the app crashes when it tries to delete the cell at that indexPath. Is that possible?
Edit:
The user interaction is this:
I can change my code to not call reloadData and instead just update the view of the cell. Is that advisable? What could happen if I don't do that?
Upvotes: 0
Views: 231
Reputation: 437552
Personally, I'd just reload the button in question with reloadRows(at:with:)
, rather than the whole table view. Not only is this more efficient, but it will avoid jarring scrolling of the list if you're not already scrolled to the top of the list.
I'd then defer the deleteRows(at:with:)
animation by some small fraction of time. I personally think 0.5 seconds is too long because a user may proceed to tap on another row and they can easily get the a row other than what they intended if they're unlucky enough to tap during the wrong time during the animation. You want the delay just long enough so they get positive confirmation on what they tapped on, but not long enough to yield a confusing UX.
Anyway, you end up with something like:
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: { $0 === addedFriend }) {
self.friendsToAdd.remove(at: row)
self.tableView.deleteRows(at: [IndexPath(row: row, section: 0)], with: .fade)
}
}
}
(Note, I used ===
because I was using a reference type. I'd use ==
with a value type that conforms to Equatable
if dealing with value types. But those are implementation details not relevant to your larger question.)
That yields:
Upvotes: 1
Reputation: 2461
Yes, probably what's happening is the table view is invalidating stored index path.
To test whether or not it is the issue try to change data that is represented in the table right before reloadData()
is called.
If it is a problem, then you'll need to use an identifier of an object represented by the table cell instead of index path. Modified code will look like this:
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)
}
}
Upvotes: 0