UITableViewCell doesn't get deselected when swiping back quickly

Solution 1:

This worked best for me:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.clearsSelectionOnViewWillAppear = NO;
}

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:animated];
}

I even got a much better unselect fading while I was swiping back slowly.

Solution 2:

I'm dealing with the same problem right now. The UICatalog-sample from Apple seems to bring the dirty solution.

It really doesn't make me happy at all. As mentioned before it uses [self.tableView deselectRowAtIndexPath:tableSelection animated:NO]; to deselect the currently selected row.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // this UIViewController is about to re-appear, make sure we remove the current selection in our table view
    NSIndexPath *tableSelection = [self.tableView indexPathForSelectedRow];
    [self.tableView deselectRowAtIndexPath:tableSelection animated:NO];

    // some over view controller could have changed our nav bar tint color, so reset it here
    self.navigationController.navigationBar.tintColor = [UIColor darkGrayColor];
}

I have to mention the sample code may not be iOS 7 iOS 8 iOS 9 iOS 10-ready


Something which really confuses me is the UITableViewController Class Reference:

When the table view is about to appear the first time it’s loaded, the table-view controller reloads the table view’s data. It also clears its selection (with or without animation, depending on the request) every time the table view is displayed. The UITableViewController class implements this in the superclass method viewWillAppear:. You can disable this behavior by changing the value in the clearsSelectionOnViewWillAppear property.

This is exactly the behavior I expect… but it does not seem to work. Neither for you nor for me. We really have to use the "dirty" solution and do it on our own.

Solution 3:

Fabio's answer works well but doesn't give the right look if the user swipes just a little bit and then changes their mind. In order to get that case right you need to save the selected index path and reset it when necessary.

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    self.savedSelectedIndexPath = nil;
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if (self.savedSelectedIndexPath) {
        [self.tableView selectRowAtIndexPath:self.savedSelectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
    }
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.savedSelectedIndexPath = self.tableView.indexPathForSelectedRow;

    if (self.savedSelectedIndexPath) {
        [self.tableView deselectRowAtIndexPath:self.savedSelectedIndexPath animated:YES];
    }
}

If using a UITableViewController, make sure to disable the built-in clearing:

self.clearsSelectionOnViewWillAppear = NO;

and add the property for savedSelectedIndexPath:

@property(strong, nonatomic) NSIndexPath *savedSelectedIndexPath;

If you need to do this in a few different classes it might make sense to split it out in a helper, for example like I did in this gist: https://gist.github.com/rhult/46ee6c4e8a862a8e66d4

Solution 4:

This solution animates the row deselection along with the transition coordinator (for a user-driven VC dismiss) and re-applies the selection if the user cancels the transition. Adapted from a solution by Caleb Davenport in Swift. Only tested on iOS 9. Tested as working with both user driven (swipe) transition and the old-style "Back" button tap.

In the UITableViewController subclass:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // Workaround. clearsSelectionOnViewWillAppear is unreliable for user-driven (swipe) VC dismiss
    NSIndexPath *indexPath = self.tableView.indexPathForSelectedRow;
    if (indexPath && self.transitionCoordinator) {
        [self.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
            [self.tableView deselectRowAtIndexPath:indexPath animated:animated];
        } completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
            if ([context isCancelled]) {
                [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
            }
        }];
    }
}

Solution 5:

After running into this myself today I found out that this apparently is a fairly well-known problem with UITableView, its support for interactive navigation transitions is slightly broken. The folks behind Castro have posted an excellent analysis and solution to this: http://blog.supertop.co/post/80781694515/viewmightappear

I decided to use their solution which also considers cancelled transitions:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    NSIndexPath *selectedRowIndexPath = [self.tableView indexPathForSelectedRow];
    if (selectedRowIndexPath) {
        [self.tableView deselectRowAtIndexPath:selectedRowIndexPath animated:YES];
        [[self transitionCoordinator] notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
            if ([context isCancelled]) {
                [self.tableView selectRowAtIndexPath:selectedRowIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
            }
        }];
    }
}