UITableView Checkmark ONLY ONE Row at a Time

52,025

Solution 1

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.

Solution 2

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
}

Solution 3

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

Solution 4

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

Solution 5

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;
}
Share:
52,025
Jupiter869
Author by

Jupiter869

Newcomer to Objective-C and xcode

Updated on October 05, 2021

Comments

  • Jupiter869
    Jupiter869 over 2 years

    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.

  • Nick Hingston
    Nick Hingston about 12 years
    The only problem with this solution is you will lose the deselect row animation. Just reload the other visible cells.
  • Phillip Mills
    Phillip Mills about 12 years
    I definitely choose correct first and pretty later...if necessary. :-)
  • Jupiter869
    Jupiter869 about 12 years
    Thank You again! reloadData was the key to making things work. Your insight much appreciated.
  • Jade
    Jade almost 11 years
    Correct and pretty is achievable in this case. So there is no need to resort to reloadData.
  • Admin
    Admin about 10 years
    Does anyone know how to save the state of the selected row using NSUserDeafults? I've been looking for a simple way to do this. All the solution I've found are rather elaborate, and for my project that would simply clutter up my code.
  • Burhanuddin Sunelwala
    Burhanuddin Sunelwala about 10 years
    @Borges could you explain your question a bit more? what exactly you mean by state of a cell?
  • Thomás Pereira
    Thomás Pereira over 9 years
    The BEST solution! Thank you!
  • p0lAris
    p0lAris almost 9 years
    This doesn't take care of reusability. Careful.
  • umakanta
    umakanta over 8 years
    It is crashing when 'checkedIndexPath' is not in the VisibleCells in the tableView if (self.checkedIndexPath != nil) { var cell = tableView.cellForRowAtIndexPath(self.checkedIndexPath!) cell?.accessoryType = .None } Cell is nil if it is not in the Visible Cells
  • Cullen SUN
    Cullen SUN over 8 years
    agree with @p0lAris. When the cell get reused. it still having the check mark
  • jeffjv
    jeffjv over 8 years
    This is fine if your table has less rows then what would fill the screen but once they spill past and start getting reused during a scroll (as @p0lAris said), your users will be very confused.
  • Deepak Thakur
    Deepak Thakur about 8 years
    how is lastSelectedIndexPath initialised and managed? as lastSelectedIndexPath can never be nil
  • Deepak Thakur
    Deepak Thakur about 8 years
    var lastSelectedIndexPath: NSIndexPath? = NSIndexPath(forRow: 0, inSection: 0)
  • Dasoga
    Dasoga about 8 years
    var lastSelectedIndexPath: NSIndexPath! Is nil at start, but after, I check if is nil with the if, and at end of the function I do: lastSelectedIndexPath = indexPath
  • thephatp
    thephatp almost 8 years
    @HardikAmal - simple, just add the condition when configuring the cell to definitively set the accessory type. So, be sure to have the else after the if and set the value either way.
  • Arun
    Arun almost 8 years
    please declare as NSINTEGER instead of int
  • jeffjv
    jeffjv over 7 years
    You don't (and really shouldn't) call reloadData to solve this issue. Please see some of the other answers (like mine) to correctly handle the animations and reusability.
  • matteodv
    matteodv over 6 years
    This answer is amazing but it assumes that selectedIndexPath is initialized, doesn't it? I mean, if there is no default value when initializing the table view, didSelectRowAtIndexPath should try to unwrap selectedIndexPath and set the value
  • jeffjv
    jeffjv over 6 years
    @matteodv, in my code snip I have a comment on the third line that says // likely you would set this during cellForRowAtIndexPath when you dequeue the cell that matches a saved user selection or the default. Does that answer your question?
  • matteodv
    matteodv over 6 years
    Yes, sorry... I meant a situation in which there isn't a default value and it need the be set the first time a row is clicked. In this case, cellForRowAtIndexPath won't set the variable...
  • Naresh
    Naresh over 5 years
    Simply this code is enough, thank you. John Paul Manoza
  • iHarshil
    iHarshil over 5 years
    Simply Superb Man! This solution also works for keep selecting anything to a cell. Great! You saved my day! :)
  • As If Prince
    As If Prince about 3 years
    you saved my day
  • pkamb
    pkamb over 2 years
    Isn't calling tableView.cellForRowAtIndexPath( ... ) essentially the same as calling tableView.reloadRows(at: ...)? If you reload the old and new rows (not the entire table view) you can remove the duplicated checkmark code from didSelect.