简体   繁体   中英

UITableView selected cell doesn't stay selected when scrolled

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.

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

Fixed my problem! Hope it would be helpful, sorry for my poor English btw.

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)

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.

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

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.

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

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 (eg 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.

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
}

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

In it, I also explain why this happens.

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

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.

不要使用内置系统属性 isSelected ,您可以创建自己的属性 eg: var isSelectedStyle = false 使用 cell.isSelectedStyle = ....

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM