4

I have a table view where I'd like to deselect either the previously selected cell when returning to it from a detail view or the newly added cell when the user creates an item.

However, since sometimes new items are added, the table is refreshed by calling reloadData in viewWillAppear:. This means none of the cells are selected when the view appears, even if I have self.clearsSelectionOnViewWillAppear = NO.

By selecting and deselecting the cell after the table view appears (in viewDidAppear:) the timing of the deselect animation is visibly different to the user (try for yourself, it's slower and doesn't feel as slick).

How should I be preserving the selection even after the table view is refreshed? (Please keep in mind, depending on the situation, I'd like the deselect either the previously selected cell or the newly created cell.) Or should somehow I be reloading the data in my table differently?

James
  • 762
  • 8
  • 22

2 Answers2

7

You could save the NSIndexPath from the - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath method and when the view reloads deselect that row.

Another way of doing this could be by passing the NSIndexPath and the current UITableViewController to the UIViewController you're creating and when that UIViewController is popped, you deselect the specific row.

When a new item is created, add one to the indexPath's row element to deselect the right row.

You could also reload only the rows that have changed:

[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                      withRowAnimation:UITableViewRowAnimationNone];

[self.tableView selectRowAtIndexPath:indexPath 
                            animated:NO
                      scrollPosition:UITableViewScrollPositionNone];

[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
Iñigo Beitia
  • 6,303
  • 4
  • 40
  • 46
  • Thanks for the reply. For method 1, where should I add the method to deselect the row? As I mentioned, adding it in `viewDidAppear:` looks visibly different to the user than the standard behavior, in say, the Contacts app (the deselect animation begins slower). Method 2 isn't working because I call `reloadData` in `viewWillAppear:` for the table view controller. The user won't see the deselect animation. Is there a better practice for refreshing the data in my table view? I need it to display updated data, but also want the deselect animation. – James Apr 26 '12 at 01:41
  • What if you just reload the rows that changed instead of the whole table? – Iñigo Beitia Apr 26 '12 at 01:53
  • 1
    This seems to get the desired effect! But why does this work, while replacing the first line with `[[self tableView] reloadData];` doesn't?? Is the table view being redrawn differently? – James Apr 26 '12 at 02:17
  • @ibeitia please take a look at my question. http://stackoverflow.com/questions/21126654/uitableview-deselects-tableviewcells-when-scrolling-away-and-back – Chisx Jan 17 '14 at 00:53
0

More advanced solution:

  • It works with [self.tableView reloadData].
  • It doesn't crash when selected row is missing after reload.

Part of code from example MyViewController.m:

@interface MyViewController ()
{
    MyViewModel* _viewModel;
    NSString* _selectedItemUniqueId;
}

@property (nonatomic, weak) IBOutlet UITableView* tableView;

@end

@implementation MyViewController

#pragma mark - UIViewController methods

- (void)viewDidLoad
{
    [super viewDidLoad];
    _selectedItemUniqueId = nil;
}

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

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
    // Get data for selected row.
    Item* item = _viewModel.data.sections[indexPath.section].items[indexPath.row];

    // Remember selection that we could restore it when viewWillAppear calls [self.tableView reloadData].
    _selectedItemUniqueId = item.uniqueId;

    // Go to details view.
}

- (void)tableView:(UITableView*)tableView willDisplayCell:(nonnull UITableViewCell *)cell forRowAtIndexPath:(nonnull NSIndexPath *)indexPath {

    // Get data for row.
    Item* item = _viewModel.data.sections[indexPath.section].items[indexPath.row];

    // Bring back selection which is destroyed by [self.tableView reloadData] in viewWillAppear.
    BOOL selected = _selectedItemUniqueId && [item.uniqueId isEqualToString:_selectedItemUniqueId];
    if (selected) {
        _selectedItemUniqueId = nil;
        [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
        [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
}

@end