UITableView滑动RowAction取消选中行:原因、回调及解决方案咨询
Is this behavior reasonable?
Yes, this is actually intentional UIKit behavior, and here’s the design logic behind it:
- Swipe actions (like delete or custom options) are built as single-row-focused interactions, separate from multi-selection mode. UIKit automatically exits multi-selection when a swipe action triggers to avoid state confusion—users might get mixed up if they’re in bulk-select mode but performing an action on one individual row.
- While it makes sense for standard use cases, it’s definitely a headache when you need to retain multi-selection state after executing a swipe action.
Are there "deselect" callbacks available?
Absolutely! You can use these UITableViewDelegate methods to track deselection events and update your selected row count display:
tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath): This fires for every row that gets deselected, including when UIKit clears all selections during a swipe action. You can use this to adjust your selected count in real time.tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath?: If you need to intercept deselection before it happens, this method lets you override or modify the behavior (though it won’t stop UIKit from clearing all selections during swipes on its own).
You could also observe changes to tableView.indexPathsForSelectedRows via KVO, but the delegate methods are far more straightforward for tracking selection state shifts.
Solutions to retain selected rows
Here are practical, actionable approaches to preserve your selected row array:
1. Maintain a custom selected index paths array
Stop relying on tableView.indexPathsForSelectedRows and create your own source of truth:
var customSelectedIndexPaths: [IndexPath] = []
- In
tableView(_:didSelectRowAt:), add the indexPath to your array (if not already present) - In
tableView(_:didDeselectRowAt:), only remove the indexPath from your array if the deselection was user-initiated (not triggered by a swipe). Use a flag likevar isPerformingSwipeAction = falseto distinguish the two scenarios. - After the swipe action completes, reset the flag and re-select the rows using
tableView.selectRow(at:animated:scrollPosition:).
Example snippet for a swipe action handler:
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { [weak self] (action, view, completion) in guard let self = self else { return } // Save current selected state before UIKit clears it let savedPaths = self.customSelectedIndexPaths // Execute your delete logic here self.data.remove(at: action.indexPath.row) self.tableView.deleteRows(at: [action.indexPath], with: .automatic) // Restore selection (skip the deleted row) let updatedPaths = savedPaths.filter { $0 != action.indexPath } updatedPaths.forEach { self.tableView.selectRow(at: $0, animated: false, scrollPosition: .none) } self.customSelectedIndexPaths = updatedPaths completion(true) }
2. Intercept swipe execution to preserve selection
When creating your UISwipeActionsConfiguration or UITableViewRowAction, capture the current selected index paths right before the action runs, then restore them once the action finishes. This works around UIKit’s automatic deselection by manually re-establishing the state.
For UITableViewRowAction, here’s how it looks:
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { [weak self] (action, indexPath) in guard let self = self else { return } let savedPaths = tableView.indexPathsForSelectedRows ?? [] // Perform delete operation self.data.remove(at: indexPath.row) tableView.deleteRows(at: [indexPath], with: .automatic) // Restore remaining selected rows let updatedPaths = savedPaths.filter { $0 != indexPath } updatedPaths.forEach { tableView.selectRow(at: $0, animated: false, scrollPosition: .none) } self.customSelectedIndexPaths = updatedPaths } return [deleteAction] }
3. Implement custom swipe gestures (advanced)
If you want full control over the interaction, skip system swipe actions entirely and build your own UIPanGestureRecognizer for table view cells. This way, UIKit won’t recognize the gesture as a system action and won’t interfere with multi-selection state. You’ll need to handle swipe animations and action presentation yourself, but it gives you complete freedom to retain multi-selection during swipes.
内容的提问来源于stack exchange,提问作者rommex

