22

I'm having problems with table view cells not keeping their "selected" state when scrolling the table. Here is the relevant code:

@property (nonatomic, strong) NSIndexPath *selectedIndexPath;

-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    self.selectedIndexPath = indexPath;
    //do other stuff
}

-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    MyCustomCell_iPhone* cell = [tableView dequeueReusableCellWithIdentifier:@"MyCustomCell_iPhone"];

    if (cell == nil)
        cell = [[[NSBundle mainBundle] loadNibNamed:@"MyCustomCell_iPhone" owner:self options:nil] objectAtIndex:0];

    if ([indexPath compare: self.selectedIndexPath] == NSOrderedSame) {
        [cell setSelected:YES animated:NO];
    }

    return cell;
}

And for the cell:

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

    if (selected) {
        self.selectedBg.hidden = NO;
    }else{
        self.selectedBg.hidden = YES;
    }
}

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

    if (highlighted) {
        self.selectedBg.hidden = NO;
    }else{
        self.selectedBg.hidden = YES;
    }
}

How can I get the selected cell to stay highlighted? If I scroll it off the screen, when it scrolls back on the screen it appears in its unselected state (with its selectedBg hidden).

EDIT: Removing the setHighlighted method from the cell fixes the issue. However that means that I get no highlighted state when pressing the table cell. I'd like to know the solution to this.

soleil
  • 12,133
  • 33
  • 112
  • 183

12 Answers12

14

Had the same problem, selected cell's accessoryView disappeared on scroll. My co-worker found pretty hack for this issue. The reason is that in iOS 7 on touchesBegan event UITableView deselects selected cell and selects touched down cell. In iOS 6 it doesnt happen and on scroll selected cell stays selected. To get same behaviour in iOS 7 try:

  1. Enable multiple selection in your tableView.

  2. Go to tableView delegate method didSelectRowAtIndexPath, and deselect cell touched down with code :

NSArray *selectedRows = [tableView indexPathsForSelectedRows];
for(NSIndexPath *i in selectedRows){
    if(![i isEqual:indexPath]) {
        [tableView deselectRowAtIndexPath:i animated:NO];
    }
}
  • 1
    Great! This solution worked fine for me. But I found a problem when the selected row is selected again. Then it gets deselected. I've added this UITableView delegate method to solve the issue: - (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; if ([cell isSelected]) { return nil; } return indexPath; } – mlabraca Jul 28 '14 at 16:47
7

I know my method is not very orthodox but seems to work. Here is my solution:

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    if cell.selected {
        cell.selected = true
    } else {
        cell.selected = false
    }
}

You must implement all the methods you mentioned on your post as well (@soleil)

bonokite
  • 425
  • 5
  • 4
  • this is the only solution here that helped me. I have implemented it in Obj-C within my `UITableViewController` subclass and it wasn't even necessary to store `selectedIndexPath` in a variable as @soleil if you use `[self setClearsSelectionOnViewWillAppear:NO];` – skornos Nov 12 '15 at 08:30
  • 1
    you can just do `cell.selected = cell.selected`, although it isn't as readable – sbru Mar 28 '17 at 18:02
4

I am using Xcode 9.0.1 and Swift 4.0. I found the following codes resolved my selection mark when cells off screen and back:

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if cell.isSelected {
        cell.accessoryType = .checkmark
    } else {
        cell.accessoryType = .none
    }
}
David.Chu.ca
  • 37,408
  • 63
  • 148
  • 190
2

iOS 7/8 both deselect the cell when scrolling begins (as Alexander Larionov pointed out).

A simpler solution for me was to implement this UIScrollViewDelegate method in my ViewController:

 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
     NSInteger    theRow       = [self currentRowIndex]; // my own method
     NSIndexPath *theIndexPath = [NSIndexPath indexPathForRow:theRow inSection:0];
     [self.myTableView selectRowAtIndexPath:theIndexPath 
                                   animated:NO 
                             scrollPosition:UITableViewScrollPositionNone];
 }

This works because my viewController is the UITableView's delegate, and UITableView inherits from UIScrollView.

lifjoy
  • 2,158
  • 21
  • 19
  • 1
    This is an alternative solution to yours NSIndexPath *indexPath = [_tableView indexPathForSelectedRow]; [_tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; – alexiscrack3 Apr 10 '15 at 18:18
1

If you want to achieve the same thing in Swift then here is the code. By the way I am using Xcode 7.2 with Swift 2.1.

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    if cell.selected == true{
        cell.selected = true
        cell.backgroundColor = UIColor.blackColor()
    }else{
        cell.backgroundColor = tableViewCellColor //Don't panic its my own custom color created for the table cells.
        cell.selected = false
    }
}

Do other customization what ever you want..

Thanks..

Hope this helped.

onCompletion
  • 6,500
  • 4
  • 28
  • 37
1

Swift 3 solution, 2017.

I fixed the problem with this simple line of code:

cell.isSelected = tableView.indexPathsForSelectedRows?.contains(indexPath) ?? false

Inside the tableView(tableView:cellForRowAt indexPath:) method:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // Dequeue a reusable cell
    if let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID") {

        cell.isSelected = tableView.indexPathsForSelectedRows?.contains(indexPath) ?? false

        // Now you can safely use cell.isSelected to configure the cell 

        // ...your configurations here

        return cell
    }

    return UITableViewCell()
}
iPruch
  • 31
  • 1
  • 4
1

Swift 5

Put the following code in your custom UITableViewCell subclass:

override func setHighlighted(_ highlighted: Bool, animated: Bool) {
    guard !isSelected else { return }

    super.setHighlighted(highlighted, animated: animated)
    if highlighted {
        // Style cell for highlighted
    } else {
        // Style cell for unhighlighted
    }
}

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    if selected {
        // Style cell for selected
    } else {
        // Style cell for unselected
    }
}

Explanation: Try setting breakpoints on both setHighlighted and setSelected. You'll find that the dequeueReusableCell method calls setSelected then setHighlighted in that order to reset the new cell. So your highlighting code is blowing away the styling you did in your selection code. The non-hack fix is to avoid destroying your selected styling when setHighlighted(false, animated: false) gets called.

Chris Chute
  • 3,229
  • 27
  • 18
0

Have you tried comparing the rows of the index paths instead of the entire index path object?

if ((indexPath.row == self.selectedIndexPath.row) && (indexPath.section == self.selectedIndexPath.section)) {
    [cell setSelected:YES animated:NO];
}
danielM
  • 2,462
  • 2
  • 20
  • 21
0

Here's the solution I came up with — and it doesn't even feel hacky.

1) Implement -scrollViewWillBeginDragging: and -scrollViewWillEndDragging:withVelocity:targetContentOffset: and manually highlight the cell for the selected row (if there is one) during scrolling.

Mine look like this:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollview {
    self.scrollViewIsDragging = YES;

    if( [self.tableView indexPathForSelectedRow] ) {
        [[self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]] setHighlighted:YES];
    }
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    self.scrollViewIsDragging = NO;

    if( [self.tableView indexPathForSelectedRow] ) {
        [[self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]] setHighlighted:NO];
    }
}

The scrollViewIsDragging property is there so that in -tableView:cellForRowAtIndexPath: we can make sure any newly dequeued cells have the proper highlighting (e.g. if the cell for the selected row is scrolled onto screen after having been off screen). The pertinent part of that method looks like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    // ... cell creation/configuration ...

    if( self.scrollViewIsDragging && [[tableView indexPathForSelectedRow] isEqual:indexPath]) {
        [cell setHighlighted:YES animated:NO];
    }

}

…and there you have it. The cell for the selectedRow will stay highlighted during scrolling.

Ben Lachman
  • 3,083
  • 1
  • 28
  • 44
  • The one caveat, which I think is the correct behavior (at least in my case), is that a hesitant touch on another cell may highlight that cell momentarily before the user starts scrolling. If anyone has a smart way of dealing with this I'd be keen to hear it. – Ben Lachman May 31 '14 at 01:56
0

UITableViewCell has a BOOL property "selected". Whenever you load the cell, check the state of selected and make selection or deselection accordingly as follows in cellForRowAtIndexPath definition:

if (cell.selected) {
    // Maintain selected state
}
else{
     // Maintain deselected state
}
Babak
  • 419
  • 2
  • 16
Apsara
  • 61
  • 6
0

Posted a quick answer to that here: https://stackoverflow.com/a/35605984/3754003

In it, I also explain why this happens.

Community
  • 1
  • 1
Dave G
  • 12,042
  • 7
  • 57
  • 83
0

Do not use built-in system properties isSelected.

You can create your own property, for example:

var isSelectedStyle = false
cell.isSelectedStyle = ....
1215ccc
  • 41
  • 5