Reputation: 3790
We have implemented swipe to delete feature for our app, but somehow we are seeing this intermittent prod crash on Crashlytics.
I have been following several StackOverflow posts about this crash, but I am unable to get the exact cause of this crash.
I have tried producing this crash several times but every time it works fine.
Any thoughts or ideas, if anything I am doing wrong here? Below is the crash report and the code snippet which is currently live.
@available(iOS 11.0, *)
private func actionForType(alertID: String, swipeAction: AlertSwipeActionType, indexPath: IndexPath) -> UIContextualAction {
let contexualAction = UIContextualAction(style: .normal, title: nil) { [weak self] (action, view, completion) in
guard let strongSelf = self else {
return
}
switch swipeAction {
......
case .affirm:
completion(true)
strongSelf.dispositionAlert(id: alertID, status: true, indexPath: indexPath)
......
}
}
......
return contexualAction
}
fileprivate func dispositionAlert(id: String, status: Bool, indexPath: IndexPath) {
let dispositionRequest = AlertDispositionUpdateBody(id: id, disposition: status, questionId: nil)
self.updateAlertDispositionStatus(request: dispositionRequest) { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.removeCellWithAnimationAt(indexPath: indexPath)
strongSelf.loadAlerts()
}
}
fileprivate func removeCellWithAnimationAt(indexPath: IndexPath) {
DispatchQueue.main.async {
self.tableView.beginUpdates() // likely not required
self.removeAlertAtIndexPath(indexPath)
self.tableView.deleteRows(at: [indexPath], with: .fade)
self.tableView.endUpdates() // likely not required either
}
}
@objc func loadAlerts() {
self.startLoadingAnimation()
self.alertsFooterList.removeAll()
AlertsManager.sharedInstance.loadMemberAlerts()
}
fileprivate func removeAlertAtIndexPath(_ indexPath: IndexPath) {
let alertStatus = self.alertTabType.statusToLoad[indexPath.section]
if let alerts = self.alertsList[alertStatus],
alerts.count > indexPath.row {
self.alertsList[alertStatus]?.remove(at: indexPath.row)
}
}
Upvotes: 2
Views: 4970
Reputation: 8563
The crash is telling you what happened: when you try to delete the row it has already been removed. Now that seems strange because the row was there when you presented the alertView. So between the time of presenting the alertView and deleting the row, the row was removed by some other source. Reading your code it is clearly possible. There are two delays between presenting the alertview and deleting the row. The first is a possibly very long time until the user confirms to delete, and the second is the DispatchQueue.main.async
which is generally pretty fast, but can still cause these types of bugs.
Instead of recording which indexPath to delete (because the indexPath may be change by the time the user confirms), record the ID of the item you are trying to delete. When the user eventually confirm the alert then lookup where it is in the tableview and delete it if you find it.
The deeper problem is that you have two sources of truth - the tableview and your datasource and they are not sync. It is better to update your datasource first, and then have code that automatically syncs the tableview. This way they always remain in sync. https://github.com/Instagram/IGListKit is a solution that already implements this, and you might get value by using it.
Upvotes: 5
Reputation: 348
If you use tableview delete row called then first call number of section. below code.
Code: swift
fileprivate func removeCellWithAnimationAt(indexPath: IndexPath) {
DispatchQueue.main.async {
self.removeAlertAtIndexPath(indexPath)
self.tableView.numberOfRows(inSection: indexPath.section) // called first
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
}
I Hope this code work.
Upvotes: 0