In apps such as Settings, when you tap on a cell which pushes a screen, then you swipe back from the left of the screen, you can see the deselection of the selected cell’s background color fading, and it's fully interactive - if you swipe half way then swipe back the selected background view returns to full opacity.
In my app, I haven't changed any of the default behavior, and when I swipe from the left to go back, the selected cell background color remains completely opaque until the swipe gesture is completed, and then it quickly fades to deselect it.
How can one implement the interactive deselection of cells via the swipe to go back gesture?
To enable interactive deselection in iOS 11 and newer, you can use UITableViewController
because it implements it for you, or you can implement it by animating the deselection alongside the transition coordinator, like so:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
if (selectedIndexPath) {
if (self.transitionCoordinator) {
[self.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self.tableView deselectRowAtIndexPath:selectedIndexPath animated:YES];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
if (context.cancelled) {
[self.tableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}];
} else {
[self.tableView deselectRowAtIndexPath:selectedIndexPath animated:animated];
}
}
}
And in Swift:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let selectedIndexPath = tableView.indexPathForSelectedRow {
if let coordinator = transitionCoordinator {
coordinator.animate(alongsideTransition: { context in
self.tableView.deselectRow(at: selectedIndexPath, animated: true)
}) { context in
if context.isCancelled {
self.tableView.selectRow(at: selectedIndexPath, animated: false, scrollPosition: .none)
}
}
} else {
self.tableView.deselectRow(at: selectedIndexPath, animated: animated)
}
}
}
A similar implementation works with UICollectionView
:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let indexPath = collectionView.indexPathsForSelectedItems?.first {
if let coordinator = transitionCoordinator {
coordinator.animate(alongsideTransition: { _ in
self.collectionView.deselectItem(at: indexPath, animated: true)
}, completion: { context in
if context.isCancelled {
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
}
})
} else {
collectionView.deselectItem(at: indexPath, animated: animated)
}
}
}
It seems as though the clearsSelectionOnViewWillAppear
might actually be getting called by viewDidAppear:
rather than viewWillAppear:
The change only happens once the transition is completely over and if you cancel the interactive transition it does not happen at all (if it was in viewWillAppear:
, it would). This looks like a UIKit
bug as the docs clearly state it should be getting called in viewWillAppear:
Put the following line of code into viewWillAppear:
and you will get the exact behavior you are looking for, I just tried it. This is probably the exact behavior that property triggers, just in the wrong method.
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With