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.
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];
}
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)
}
}
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.
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.
Swift 3 Solution
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
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.
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.
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;
}
Swift 5 Solution:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
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.
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:
Write the unwind segue (or paste from above) into your 1st VC (the one with the table)
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.
Select the unwind segue you created in step 1
Good luck.
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)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
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.
Use this method in UITableViewCell class
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
// Just comment This line of code
// [super setSelected:selected animated:animated];
}
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
}
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()
}
}
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.
Related
I have a button called addSet at the end of each section of my tableView, it is used as a footerView and it is supposed to tell the UITableViewController of when it is pressed and in which section. My code for the custom table view cell is as follows
import UIKit
class FooterTableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
var footerDelegate:FooterTableViewCellDelegate?
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func addSetIsPressed(_ sender: AnyObject) {
print("Add Set is pressed")
footerDelegate?.didAddSetIsPressed(cell:self)
}
}
protocol FooterTableViewCellDelegate {
func didAddSetIsPressed(cell:FooterTableViewCell)
}
And in my TableViewController, I implement it like so
func didAddSetIsPressed(cell: FooterTableViewCell) {
let indexPath = tableView.indexPath(for: cell)
print("Index path is \(indexPath)")
}
I want to get the indexPath (the section specifically) when the user taps my button, however it always returns nil. What am I doing wrong?
To put things in context. I am using this cell as a footerView, so the cell is implemented like so
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "footerCell") as! FooterTableViewCell
cell.footerDelegate = self
return cell
}
so it isn't implemented in cellForRow at indexPath like it would normally be
Thanks in advance.
The thing is you put the cell FooterTableViewCell as a viewForFooterInSection,
so it's not used as a UITableViewCell in the UITableView, so the UITableView is not holding the indexPath of this UITableViewCell "Cause i said previously, the cell's view only is used as a footerView"
You need to add the button inside the cell that's being rendered on the UITableView. "The one that's being returned in the tableView(_:cellForRowAt:) method"
On a side note i noticed that you have a variable named footerDelegate in your cell, it needs to be weak to avoid memory leaks as you assign your TableViewController as this delegate,
so the UITableViewCell holds a strong reference of the TableViewController that leads to memory leak cause also in the view hierarchy the TableViewController contains the UITableView as a subView.
I found out how to do it, in order to detect the section in which the button was tapped. There must be an outlet reference in the FooterCell and in the tableViewController, in viewForFooter in Section, just add the following line
cell.addSetOutlet.tag = section
I have a tableView with several section and each section contains only one cell (this is done to create a gab between each cell). Im trying to create a custom selection view for my cell when it is selected.
when I select a row, the custom selection view is being added to more than one cell. I know the problem is because cells are being reused. What is the best suitable solution to overcome this problem?
This is my code.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let myCell = tableView.cellForRowAtIndexPath(indexPath) as? PredefinedServicesCell{
let selectionView = UIView()
selectionView.backgroundColor = UIColor(hex: 0x3399CC).colorWithAlphaComponent(0.2)
selectionView.layer.cornerRadius = (myCell.containerView.layer.cornerRadius)
selectionView.frame = (myCell.containerView.frame)
myCell.containerView.addSubview(selectionView)
}
}
You can make you custom selected view hide or show in the following method in your CustomCell Class
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.customSelectedView.hidden = !selected
}
When I use tableView.indexPathForSelectedRow(), calling selectRowAtIndexPath works perfectly. However, I am selecting multiple rows thus I am trying to use table.View.indexPathForSelectedRows() as [NSIndexPath]
Swift Compiler Error: NSIndexPath is not a subtype of NSIndexPath
Error has been resolved.
Edit Added For Loop, multiple selection still not happening.
//I initialized saveSelection globally
var saveSelection : [NSIndexPath]?
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
saveSelection = (drugTableView.indexPathsForSelectedRows() as [NSIndexPath])
}
override func viewWillAppear(animated: Bool)
{
if !selectedDrugs.isEmpty
{
for save : NSIndexPath in saveSelection! as [NSIndexPath]
{
self.drugTableView.selectRowAtIndexPath(save, animated: true, scrollPosition: UITableViewScrollPosition.None)
}
}
}
Thank you for your help.
Here's what I did:
Made a dictionary of IndexPaths. Used didSelectRowAtIndexPath and didDeselectRowAtIndexPath to add and remove key/value pairs to the dictionary.
Inside viewWillAppear I used var indexForLoop = myDictionary.values.array to as the indexPath set to pass into my For Loop
Crossed my fingers and hoped for the best
thank you Lyndsey for your help!
I have a static cell and when clicked it launches a modal view. Except when i return from the modal view the cell is still selected? Why is it doing this and how can I make it only make the cell selected until the modal completely covers the view.
Thanks in advance
For Swift 2 :
override func viewWillAppear(animated: Bool) {
if let indexPath = self.tableView.indexPathForSelectedRow {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
For Swift 3 and 4 :
override func viewWillAppear(animated: Bool) {
if let indexPath = self.tableView.indexPathForSelectedRow {
tableView.deselectRow(at: indexPath, animated: true)
}
}
If you use a UITableViewController instead of a UIViewController this will be done automatically. Otherwise, you need to do the deselecting yourself using
deselectRowAtIndexPath:animated: on the UITableView. The best place to do this is probably on viewDidAppear: of the presenting view controller. That way, the user still sees the deselecting animation allowing them to reorient themselves.
If you don't need to track the selected row for other purposes, you can use
indexPathForSelectedRow to determine which index path needs to be deselected (if any).
I know this is too late but may help someone who's using Swift -
This will give a nice effect when you return to masterViewController from detailViewController
override func viewWillAppear(animated: Bool)
{
if let indexPath = self.tableView.indexPathForSelectedRow
{
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
Otherwise add this delegate method of TableView and call it from didSelectRowAtIndexPath
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath)
{
if let indexPath = tableView.indexPathForSelectedRow
{
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
You can use this in your UITableViewController;
- (void)viewWillAppear:(BOOL)animated
{
[yourTableView deselectRowAtIndexPath:[yourTableView indexPathForSelectedRow] animated:YES];
}
In your condition, yourTableView property should be self.tableView
swift 3
override func viewWillAppear(animated: Bool) {
tableView.deselectRow(at: tableView.indexPathForSelectedRow!, animated: true)
}
My app calls a block in tableView:didSelectRowAtIndexPath and in the block it presents a view controller. If I click the cell second time when the first click is in progress, it crashes.
How can I prevent the cell to be clicked second time?
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
[dataController fetchAlbum:item
success:^(Album *album) {
...
...
[self presentViewController:photoViewController animated:YES completion:nil];
}];
At the beginning of didSelectRow, turn off user interaction on your table.
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
tableView.userInteractionEnabled = NO;
...
You may want to turn it back on later in the completion of fetchAlbum (Do this on the main thread) so that if the user comes back to this view (or the fetch fails), they can interact with the table again.
For swift 3 :
When user select a row, turn off user interactions :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.isUserInteractionEnabled = false
Don't forget to turn it on back whenever the view appear :
override func viewDidAppear(_ animated: Bool) {
tableView.isUserInteractionEnabled = true
}
You could either prevent multiple clicks (by disabling the table or covering it up with a spinner) or you could make didSelectRowAtIndexPath present your view controller synchronously and load your "album" after it's been presented. I'm a fan of the latter as it makes the UI feel more responsive.
For a cleaner approach:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
defer {
tableView.isUserInteractionEnabled = true
}
tableView.isUserInteractionEnabled = false
}
This way you're much less prone to errors due to forgetfulness.
You want to disable user interaction on the cell:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.userInteractionEnabled = NO;
As Stonz2 points out, you probably want to do it to the entire tableview though, rather than the specific cell if you're presenting a VC.
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell?.userInteractionEnabled = false
then
set back to true before you navigate to another view, otherwise when you return back to your table the cell will be still disabled.
I have a similar approach like Skaal answered but in a different way. This solution will work for any swift version .
Create a property named isPresentingVC in your view controller and set it to true.
var isPresentingVC: Bool = true
Inside didSelect row, try this
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if isPresentingVC {
isPresentingVC = false
//do your work like go to another view controller
}
}
Now in viewWillAppear or viewDidDisappear reset its value to true
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
isPresentingVC = true
}