151

What would cause a table view cell to remain highlighted after being touched? I click the cell and can see it stays highlighted as a detail view is pushed. Once the detail view is popped, the cell is still highlighted.

shim
  • 9,289
  • 12
  • 69
  • 108
4thSpace
  • 43,672
  • 97
  • 296
  • 475

18 Answers18

268

In your didSelectRowAtIndexPath you need to call deselectRowAtIndexPath to deselect the cell.

So whatever else you are doing in didSelectRowAtIndexPath you just have it call deselectRowAtIndexPath as well.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 // Do some stuff when the row is selected
 [tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Mihir Oza
  • 2,768
  • 3
  • 35
  • 61
paulthenerd
  • 9,487
  • 2
  • 35
  • 29
  • 11
    I prefer calling the `deselectRowAtIndexPath` in my viewDidAppear, if select the row brings up a new view. – notnoop Dec 03 '09 at 15:59
  • IIRC, that's where UITableViewController calls it. – Daniel Tull Dec 03 '09 at 17:48
  • 5
    Actually that is not the right place to call it, really... try using an Apple app with tables (Contacts is a good one) and you'll see after you push out to a new screen, on return the cell is still highlighted briefly before being deselected. In theory I think you do not have to do any extra work to have it deselect right away on its own, so code in viewDidAppear should not be needed... – Kendall Helmstetter Gelner Dec 03 '09 at 20:40
  • @Kendall: Yes - that is exactly what I want, which seems it should be the default behavior. However, mine stays selected upon return. Do you have any ideas? – 4thSpace Dec 03 '09 at 22:31
  • @4thSpace how are you calling deselect now? – paulthenerd Dec 03 '09 at 23:13
  • I'm not calling deselect and not sure what I did to cause it to get stuck. – 4thSpace Dec 04 '09 at 05:50
  • 6
    @Kendall, @4thSpace: Maybe my last comment was confusing as to who I was referring to, apologies for that. UITableViewController calls the `-deselectRowAtIndexPath:animated:` method on its tableView property from `-viewDidAppear`. However, if you have a table view in a UIViewController subclass, you should call `-deselectRowAtIndexPath:animated:` from `-viewDidAppear` yourself. :) – Daniel Tull Dec 04 '09 at 12:19
  • 6
    In my subclass of UITableViewController it was actually an override of `-viewWillAppear:` that broke it. Adding a call to `[super viewWillAppear:animated]` got it working again. – Ben Challenor Jul 06 '11 at 09:38
  • 5
    Since 3.2, the automatic deselection behaviour occurs if your `UITableViewController`'s `clearsSelectionOnViewWillAppear` property is set to `YES` (which is the default), and you haven't prevented `[super viewWillAppear]` from being called. – Defragged Oct 31 '11 at 10:26
  • 1
    ...or if you're not using a uitableviewcontroller use the solution here in your viewWillAppear: https://stackoverflow.com/questions/6686203/how-to-change-clearsselectiononviewwillappear-when-not-using-uitableviewcontroll – cloudsurfin Apr 29 '14 at 21:41
  • @KendallHelmstetterGelner isn't that: *you shouldn't need to manually de-select a cell, you are listening to the callbacks from the collectionView. You must have done something to necessitate calling deselected.* ? – mfaani Dec 29 '16 at 16:21
  • so then why do you have to this from the beginning?! @KendallHelmstetterGelner – mfaani Dec 29 '16 at 18:51
  • @Honey You just need to do it manually when you are not otherwise listing for callbacks or using the TableView/CollectionView controller classes... – Kendall Helmstetter Gelner Dec 29 '16 at 19:00
  • In case you push to a vc the selection action will be seen as animated. The user will see the highlight and dehighlight. @notnoop's solution solves this problem. – Onur Tuna Jul 04 '19 at 11:18
63

The most clean way to do it is on viewWillAppear:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    // Unselect the selected row if any
    NSIndexPath*    selection = [self.tableView indexPathForSelectedRow];
    if (selection) {
        [self.tableView deselectRowAtIndexPath:selection animated:YES];
    }
}

This way you have the animation of fading out the selection when you return to the controller, as it should be.

Taken from http://forums.macrumors.com/showthread.php?t=577677

Swift version

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // deselect the selected row if any
    let selectedRow: IndexPath? = tableView.indexPathForSelectedRow
    if let selectedRowNotNill = selectedRow {
        tableView.deselectRow(at: selectedRowNotNill, animated: true)
    }
}
pchmelar
  • 13
  • 1
  • 4
Danail
  • 10,443
  • 12
  • 54
  • 76
  • 1
    This is the one that I expect most people will want if they don't use a UITableViewController. +1 – MikeB Dec 14 '12 at 13:02
  • 3
    I wonder what people are doing now that iOS 7 allows the user to 'drag' back. Partially dragging but then not completing the action will fire viewWillAppear. When the user returns for real, the row will not be selected. – Ben Packard Oct 21 '13 at 19:14
  • 1
    @BenPackard just call it on `viewDidAppear` instead. – squarefrog Mar 20 '14 at 14:43
  • @Jessica The `indexPathForSelectedRow` call can return nil so it should be checked for. [The documentation states](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/indexPathForSelectedRow): *An index path identifying the row and section indexes of the selected row, or nil if the index path is invalid.* – Gordonium Jun 30 '15 at 14:38
  • this should remark as the accepted answer, it's more UI friendly so the user can reach the selection after getting back, perfect – Yahya Alshaar Feb 18 '18 at 07:35
22

For the Swift users, add this to your code:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

It's paulthenerd's answer but in Swift instead of Obj-C.

Rutger Huijsmans
  • 2,330
  • 2
  • 30
  • 67
  • 1
    doesn't it get deselected by itself, I mean the moment you let go? – mfaani Dec 29 '16 at 18:05
  • @Honey Possibly so in a previous version of iOS, or upon the view disappearing, but certainly not now in iOS 10 (likely even earlier) and above. – Jamie Birch Oct 03 '17 at 21:45
20

Did you subclass -(void)viewWillAppear:(BOOL)animated? The selected UITableViewCell won't deselect when you don't call [super viewWillAppear:animated]; in your custom method.

HansPinckaers
  • 1,755
  • 15
  • 18
11

Swift 3 Solution

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
      tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
Oscar Falmer
  • 1,771
  • 1
  • 24
  • 38
8

If you are using a UITableViewCell, then comment the following line

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{

 // [super setSelected:selected animated:animated];

}

Hope this helps.

Parth Bhatt
  • 19,381
  • 28
  • 133
  • 216
Shantanu
  • 3,086
  • 3
  • 25
  • 32
4

Updated with Swift 4

After few experiments, also based of previous answers, I've got the conclusion that the best behaviour can be achieved in 2 ways: (almost identical in practice)

// First Solution: delegate of the table View
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: false)
}

// Second Solution: With the life cycle of the view.
override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)

    let selectedRow: IndexPath? = tableView.indexPathForSelectedRow
    if let selectedRow = selectedRow {
        tableView.deselectRow(at: selectedRow, animated: false)
    }
}

I'm personally adopting the first solution, because it's simply more concise. Another possibility, if you need a little animation when you return to your tableView, is to use viewWillAppear:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    let selectedRow: IndexPath? = _view.tableView.indexPathForSelectedRow
    if let selectedRow = selectedRow {
        _view.tableView.deselectRow(at: selectedRow, animated: true)
    }
}

Last but not least, if you're using a UITableViewController, you can also take advantage of the property clearsSelectionOnViewWillAppear.

Alessandro Francucci
  • 1,528
  • 17
  • 25
3

Swift 5 Solution:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
Zog
  • 1,094
  • 2
  • 11
  • 24
3

To get the behaviour Kendall Helmstetter Gelner describes in his comment, you likely don't want deselectRowAtIndexPath but rather the clearsSelectionOnViewWillAppear property on your controller. Perhaps this was set to YES by accident?

See the comment in the default Apple template for new UITableViewController subclasses:

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;
}
Dave Teare
  • 509
  • 3
  • 7
2

I was getting this problem as well for my drill-down application. After a viewcontroller, which I'll call VC, returns after pushing another ViewController, the selected cell in VC remained highlighted. In my app, I had created VC to handle the second level (out of three levels) of my drill-down.

The problem in my case is that VC was a UIViewController (that contained a View that contained a TableView). I instead made VC a UITableViewController (that contained a TableView). The UITableViewController class automatically handles the de-highlighting of the table cell after returning from a push. The second answer to the post "Issue with deselectRowAtIndexPath in tableView" gives a more complete answer to this problem.

The problem did not occur for the root viewcontroller because when I created the app as a "Navigation-based App" in XCode, the resulting root viewcontroller was already made to subclass UITableViewController.

Community
  • 1
  • 1
stackoverflowuser2010
  • 38,621
  • 48
  • 169
  • 217
1

If none of these work for you, consider this work-around:

Use an unwind segue to call:

@IBAction func unwind_ToTableVC (segue: UIStoryboardSegue) {
    if let index = tableView.indexPathForSelectedRow {
        tableView.deselectRowAtIndexPath(index, animated: true)
    }
}

Why do this? Primarily if you're having trouble getting the deselect code to run at the right time. I had trouble with it not working on the viewWillAppear so the unwind worked a lot better.

Steps:

  1. Write the unwind segue (or paste from above) into your 1st VC (the one with the table)

  2. Go to the 2nd VC. Control-drag from the Cancel/Done/Etc button you're using to dismiss that VC and drag to the Exit Icon at the top.

  3. Select the unwind segue you created in step 1

    Good luck.

Community
  • 1
  • 1
Dave G
  • 12,042
  • 7
  • 57
  • 83
  • This is the best solution for times when viewDidAppear doesn't fire (i.e. if child view is presented modally as a form sheet and doesn't cover whole screen). – pheedsta Nov 06 '18 at 23:36
1

I am using CoreData so the code that worked for me was a combination of ideas from various answers, in Swift:

override func viewDidAppear(_ animated: Bool) {
    if let testSelected = yourTable.indexPathForSelectedRow {
        yourTable.deselectRow(at: testSelected, animated: true)
    }
    super.viewDidAppear(true)
}
Yewzr
  • 11
  • 2
1
 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
Mob
  • 53
  • 2
  • 7
0

I've been having the same issue for long time so in case anyone else is struggling:

Take a look at your -tableView: cellForRowAtIndexPath: and see if you are creating cells or using a 'reuse identifier'. If the latter, make sure that your table in IB has a cell with that identifier. If you're not using a reuse Identifier just create a new cell for each row.

This should then give your table the expected 'fade selected row' on appearing.

Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170
0

Use this method in UITableViewCell class

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {

   // Just comment This line of code
   // [super setSelected:selected animated:animated];        
}
Kampai
  • 22,848
  • 21
  • 95
  • 95
sunny
  • 9
  • 4
0

For Swift 3: I would prefer it to use in viewDidDisappear

Define:-

    var selectedIndexPath = IndexPath()

In viewDidDisappear:-

    override func viewDidDisappear(_ animated: Bool) {
    yourTableView.deselectRow(at: selectedIndexPath, animated: true)
}

In didSelectRowAtIndexPath:-

    func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
    selectedIndexPath = indexPath
}
Irshad Qureshi
  • 807
  • 18
  • 41
0

if the cell is remaining highlighted after touching it, you can call UITabelView method,

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

`[tableView deselectRowAtIndexPath:indexPath animated:YES];`   

}

Or, you can use the following method and modify it according to your requirements,

// MARK: UITableViewDelegate

func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath) {
  if let cell = tableView.cellForRowAtIndexPath(indexPath) {
     cell.backgroundColor = UIColor.greenColor()
  }
}

func tableView(tableView: UITableView, didUnhighlightRowAtIndexPath indexPath: NSIndexPath) {
  if let cell = tableView.cellForRowAtIndexPath(indexPath) {
     cell.backgroundColor = UIColor.blackColor()
  }
} 
0

Xcode 10, Swift 4

I had this same issue and discovered I left an empty call to viewWillAppear at the bottom of my tableViewController. Once I removed the empty override function the row no longer stayed highlighted upon return to the tableView view.

problem func

override func viewWillAppear(_ animated: Bool) {
  // need to remove this function if not being used.
}

removing empty function solved my problem.

Robac
  • 405
  • 4
  • 7