Jupiter869
Jupiter869

Reputation: 657

UITableView Checkmark ONLY ONE Row at a Time

With this code, I can check multiple rows in a table.

But what I want is to only have one row checked at a time. Here's my code:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    UITableViewCell *theCell = [tableView cellForRowAtIndexPath:indexPath];
    
    if (theCell.accessoryType == UITableViewCellAccessoryNone) {
        theCell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else if (theCell.accessoryType == UITableViewCellAccessoryCheckmark) {
        theCell.accessoryType = UITableViewCellAccessoryNone;
    }
}

If a user selects a different row, then I want the old row to simply automatically uncheck. Don't know how to do that.

Upvotes: 21

Views: 53504

Answers (18)

pkamb
pkamb

Reputation: 34983

Store the selected index path, update it in didSelectRowAt:, and from there call reloadRows(at: to reload only the old and new checked rows (not the entire table view):

var selectedIndexPath: IndexPath?

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let previous = selectedIndexPath
    selectedIndexPath = indexPath
    
    let allowDeselection = true // optional behavior
    if allowDeselection && previous == selectedIndexPath {
        selectedIndexPath = nil
    }
    
    tableView.reloadRows(at: [previous, selectedIndexPath].compactMap({ $0 }), with: .automatic)
    tableView.deselectRow(at: indexPath, animated: true)
}

Handle the setting of the checkmark accessory view in cellForRowAt indexPath:. This will account for cell reuse and also be triggered by your above calls to reloadRows(at:, avoiding duplicating the code of setting the cell accessory:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "IDENTIFIER", for: indexPath)
    
    cell.accessoryType = (indexPath == selectedIndexPath) ? .checkmark : .none
    
    // ...
    
    return cell
}

Upvotes: 0

Vinod Joshi
Vinod Joshi

Reputation: 7852

Swift iOS

 var checkedIndexPath : NSIndexPath?

// table row which row was selected
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    
    println("Section #\(indexPath.section)! You selected cell #\(indexPath.row)!")
    
    if (self.checkedIndexPath != nil) {
        var cell = tableView.cellForRowAtIndexPath(self.checkedIndexPath!)
        cell?.accessoryType = .None
    }
    
    var cell = tableView.cellForRowAtIndexPath(indexPath)
    cell?.accessoryType = .Checkmark
    
    self.checkedIndexPath = indexPath
    
}// end tableView

Upvotes: 0

Ranganatha G V
Ranganatha G V

Reputation: 59

Simplest way is to save the selected IndexPath and check it in cellForRowAtIndexPath:

  1. Initialize the selected index path:

    selectedIndexPath = [[NSIndexPath alloc] init];
    
  2. In cellForRowAtIndexPath:

    if([selectedIndexPath isEqual:indexPath]){
        [checkMark setHidden:NO];
    } else {
        [checkMark setHidden:YES];
    }
    
  3. In didSelectRowAtIndexPath:

    selectedIndexPath = indexPath;
    [tableView reloadData];
    

It should work work try this Njoy :)

Upvotes: 4

Phillip Mills
Phillip Mills

Reputation: 31016

The best way is to set the accessory type in cellForRowAtIndexPath.

Use didSelectRowAtIndexPath to only record which path should be selected and then call reloadData.

Upvotes: 26

Ujwal Manjunath
Ujwal Manjunath

Reputation: 2970

Objective-C:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath   *)indexPath
{
     [tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
}

-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath 
{
    [tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
}

Swift:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}

func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    tableView.cellForRow(at: indexPath)?.accessoryType = .none
}

Upvotes: 88

mhossain495
mhossain495

Reputation: 11

Swift 5 version of jeffjv's response.

// for saving currently selected index path
var selectedIndexPath: NSIndexPath? = NSIndexPath(forRow: 0, inSection: 0)  // you wouldn't normally initialize it here, this is just so this code snip works
// likely you would set this during cellForRowAtIndexPath when you dequeue the cell that matches a saved user selection or the default

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
    // this gets rid of the grey row selection.  You can add the delegate didDeselectRowAtIndexPath if you want something to happen on deselection
    tableView.deselectRow(at: indexPath, animated: true) // animated to true makes the grey fade out, set to false for it to immediately go away

    // if they are selecting the same row again, there is nothing to do, just keep it checked
    if indexPath == selectedIndexPath! as IndexPath {
        return
    }

    // toggle old one off and the new one on
    let newCell = tableView.cellForRow(at: indexPath)
    if newCell?.accessoryType == UITableViewCell.AccessoryType.None {
        newCell?.accessoryType = UITableViewCell.AccessoryType.Checkmark
    }
    let oldCell = tableView.cellForRow(at: selectedIndexPath! as IndexPath)
    if oldCell?.accessoryType == UITableViewCell.AccessoryType.Checkmark {
        oldCell?.accessoryType = UITableViewCell.AccessoryType.None
    }

    selectedIndexPath = indexPath as NSIndexPath  // save the selected index path

    // do whatever else you need to do upon a new selection
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
    // if this is the currently selected indexPath, set the checkmark, otherwise remove it
     if indexPath == selectedIndexPath! as IndexPath {
        cell.accessoryType = UITableView.CellAccessoryType.Checkmark
    } else {
        cell.accessoryType = UITableView.CellAccessoryType.None
    }
}

Upvotes: 0

Vladimir Pchelyakov
Vladimir Pchelyakov

Reputation: 650

You can do it in custom cell type in one line of code.

final class CheckableTableViewCell: UITableViewCell {

  override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    self.accessoryType = selected ? .checkmark : .none
  }

}

To make this method work - your cell should be selected.

If you don’t want to see the highlight of the selected cell, just set cell's selectionStyle to .none in storyboard or in code

This method will not work, if you will call

tableView.deselectRow(at: indexPath, animated: true)

Also works great with multiple selection.

Upvotes: 0

Vinay Krishna Gupta
Vinay Krishna Gupta

Reputation: 475

For Swift 3 following worked for me :

override func viewDidLoad() {
    super.viewDidLoad()

    // allow cells to be selected
    tableView.allowsSelection = true

    // ensure that deselect is called on all other cells when a cell is selected
    tableView.allowsMultipleSelection = false
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableVIew.cellForRow(at: indexPath as IndexPath)?.accessoryType = .checkmark
}

func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    tableVIew.cellForRow(at: indexPath as IndexPath)?.accessoryType = .none
}

Upvotes: 5

hardik
hardik

Reputation: 199

tested and solved try this its worked perfectly

add this as global variable

var selectedIndexPath = NSIndexPath(row: 0, section: 0)

Add this in didselect method

        // old one check off
        if indexPath != selectedIndexPath as IndexPath {
            let oldCell = categorytable.cellForRow(at: selectedIndexPath as IndexPath)
            if oldCell?.accessoryView != nil {
                oldCell?.accessoryView = nil
            }
            else {
                let imageName = "checkmark"
                let image: UIImageView = UIImageView(image: UIImage(named: imageName))
                cell.accessoryView = image
            }
        }
        // the new one on

        if  cell.accessoryView == nil {
            let imageName = "checkmark"
            let image: UIImageView = UIImageView(image: UIImage(named: imageName))
            cell.accessoryView = image
        }
        else {
            cell.accessoryView = nil
        }

Upvotes: 0

gchriswill
gchriswill

Reputation: 3

Here's what I came up when I had this problem.
This code is implemented in the latest version of Swift, as of today...
For more info and/or the extension file, please check out the Github Gist of this snippet.

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {       
    if let sip = selectedIndex {
        tableView.deselectRow(at: sip, animated: false)
    }
    selectedIndex = indexPath
}

override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    if selectedIndex?.row == indexPath.row {
        selectedIndex = nil
    }
}

Upvotes: 0

protspace
protspace

Reputation: 2147

My way is deselect other cells during selecting:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = ....

    if condition {
        cell.accessoryType = .checkmark
        tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.bottom)
    } else {
        cell.accessoryType = .none
    }

    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    for row in 0...tableView.numberOfRows(inSection: 0) {
        if row == indexPath.row {continue}
        tableView.deselectRow(at: IndexPath(row: row, section: 0), animated: true)
    }

    if let cell = tableView.cellForRow(at: indexPath) {
        cell.accessoryType = .checkmark
    }
}

func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRow(at: indexPath)
    cell?.accessoryType = .none
}

Upvotes: 0

msmq
msmq

Reputation: 1328

In Swift, following works perfectly:

lastSelectedIndexPath = NSIndexPath(forRow: 1, inSection: 0)//Row will be the previous selected row


    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
    let cell:LabelsSelectionCell = tableView.dequeueReusableCellWithIdentifier("LabelsSelectionCell", forIndexPath: indexPath) as! LabelsSelectionCell
    cell.setCellLael(labelOptionsArray[indexPath.row])
    if lastSelectedIndexPath == indexPath
    {
        cell.setCellCheckedStatus(true)
    }
    return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
    if let _ = lastSelectedIndexPath
    {
        let lastCell:LabelsSelectionCell = tableView.cellForRowAtIndexPath(lastSelectedIndexPath!) as! LabelsSelectionCell
        lastCell.setCellCheckedStatus(false)
    }
    let currentCell:LabelsSelectionCell = tableView.cellForRowAtIndexPath(indexPath) as! LabelsSelectionCell
    currentCell.setCellCheckedStatus(true)

    lastSelectedIndexPath = indexPath

    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

Upvotes: 1

Dasoga
Dasoga

Reputation: 5695

In Swift 2.0 I used this:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

    if((lastSelectedIndexPath) != nil) {
        let lastCell = tableView.cellForRowAtIndexPath(lastSelectedIndexPath)
        lastCell?.accessoryType = UITableViewCellAccessoryType.None
    }

    let currentCell = tableView.cellForRowAtIndexPath(indexPath)
    currentCell?.accessoryType = UITableViewCellAccessoryType.Checkmark

    lastSelectedIndexPath = indexPath

}

Upvotes: 0

jeffjv
jeffjv

Reputation: 3509

You don't need to (and shouldn't) just reload the table after each selection.

Apple has good documentation on how to manage a selection list. See Listing 6-3 for an example.

This is more or less the same answer as some of the others but I thought I'd add a little more detail.

What you want to do is save the current selected IndexPath to a variable and use that in didSelectRowAtIndexPath to remove the old check mark. This is also where you will be adding the new check mark.

You need to make sure to also set/unset the checkmarks in cellForRowAtIndexPath otherwise if your list is large and cells are reused it will look like multiple rows are selected. This is a problem with some of the other answers.

See swift 2.0 example below:

// for saving currently seletcted index path
var selectedIndexPath: NSIndexPath? = NSIndexPath(forRow: 0, inSection: 0)  // you wouldn't normally initialize it here, this is just so this code snip works
// likely you would set this during cellForRowAtIndexPath when you dequeue the cell that matches a saved user selection or the default

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    // this gets rid of the grey row selection.  You can add the delegate didDeselectRowAtIndexPath if you want something to happen on deselection
    tableView.deselectRowAtIndexPath(indexPath, animated: true) // animated to true makes the grey fade out, set to false for it to immediately go away

    // if they are selecting the same row again, there is nothing to do, just keep it checked
    if indexPath == selectedIndexPath {
        return
    }

    // toggle old one off and the new one on
    let newCell = tableView.cellForRowAtIndexPath(indexPath)
    if newCell?.accessoryType == UITableViewCellAccessoryType.None {
        newCell?.accessoryType = UITableViewCellAccessoryType.Checkmark
    }
    let oldCell = tableView.cellForRowAtIndexPath(selectedIndexPath!)
    if oldCell?.accessoryType == UITableViewCellAccessoryType.Checkmark {
        oldCell?.accessoryType = UITableViewCellAccessoryType.None
    }

    selectedIndexPath = indexPath  // save the selected index path

    // do whatever else you need to do upon a new selection
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
    // if this is the currently selected indexPath, set the checkmark, otherwise remove it
    if indexPath == selectedIndexPath {
        cell.accessoryType = UITableViewCellAccessoryType.Checkmark
    } else {
        cell.accessoryType = UITableViewCellAccessoryType.None
    }
}

Upvotes: 23

Burhanuddin Sunelwala
Burhanuddin Sunelwala

Reputation: 5343

You don't need to reload the tableView.

See the below code:

Declare a NSIndexPath lastSelectedIndexPath variable for your class

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

    if(lastSelectedIndexPath) {

        UITableViewCell *lastCell = [tableView cellForRowAtIndexPath:lastSelectedIndexPath];
        [lastCell setAccessoryView:nil];
    }

    UITableViewCell *currentCell = [tableView cellForRowAtIndexPath:currentIndexPath];
    [currentCell setAccessoryView:UITableViewCellAccessoryCheckmark];

    lastSelectedIndexPath = indexPath;
}

Upvotes: 8

alcedo
alcedo

Reputation: 1672

Try this:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if indexPath.section == 1 {
        // un-select the older row
        if let selected = self.LastSelected {
            tableView.cellForRowAtIndexPath(selected)?.accessoryType = .None
        }

        // select the new row
        tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = UITableViewCellAccessoryType.Checkmark
        self.LastSelected = indexPath
    }
}

Upvotes: 1

John Paul Manoza
John Paul Manoza

Reputation: 1735

You can create a new variable to track the index selected at didSelectRowAtIndex.

int selectedIndex;

in your cellForRowAtIndexPath

if(indexPath.row == selectedIndex)
{
   cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
   cell.accessoryType = UITableViewCellAccessoryNone;
}

and in your didSelectRowAtIndex

selectedIndex = indexPath.row;
[tableView reloadData];

Upvotes: 27

mohsinj
mohsinj

Reputation: 143

I think https://stackoverflow.com/a/5960016/928599 will help you.
I used it and it Works with deselectRowAtIndexPath too!

Upvotes: 4

Related Questions