Interactively deselect selected cell with swipe back gesture

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];

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)
        }
    }
}