48

Hi there I have situation where in an iPad app my master controller has list and details controller have its details , a typical UISplitViewController pattern. What I want to achieve is , my first row should be initially selected and later I want to give selection choice to user. I am using cell's setSelected and in didSelectRowAtIndexPath method removing selection like this.

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
    NSIndexPath* path = [NSIndexPath indexPathForRow:0 inSection:0];
    UITableViewCell* cell = [tableView cellForRowAtIndexPath:path];
    [cell setSelected:NO];
} 

for initial cell but my none cell is getting selected after that please help me out.

Sagar In
  • 591
  • 1
  • 4
  • 8

13 Answers13

89

For the cell to appear selected, you have to call -setSelected:animated: from within -tableView:willDisplayCell:forRowAtIndexPath: like so:

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (/* should be selected */) {
        [cell setSelected:YES animated:NO];
    }
}

Calling -setSelected from anywhere else has no effect.

Rafał Sroka
  • 39,540
  • 23
  • 113
  • 143
Drew C
  • 6,408
  • 3
  • 39
  • 50
  • 30
    This answer will be great if you could add the following line [tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; This is allow the user to deselect the cell. If this line is missing the line cannot be unselected. – cateof Sep 11 '15 at 11:43
  • 4
    But if you want use [tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; you have to paste it in [tableView:cellForRowAtIndexPath:] because in [tableView:weillDisplayCell:] it will NOT work. – CHiP-love-NY Nov 20 '15 at 16:54
  • 2
    @cateof 's comment should be added to the answer! Very important. – Tom Bevelander Sep 16 '16 at 18:11
  • 2
    This answer is wrong, as it will only call the animation block and not actually mark it as selected- which will break `didDeselectRowAt`. Use `tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)` – Oscar Apeland Jan 26 '18 at 15:27
  • This answer has a problem: if the user manually deselects one of the preselected cells, then scrolls so that the cell is out of the view and finally scrolls to return to that cell, `tableView(_:willDisplay:forRowAt:)` is called again and because the condition to decide if it should be selected still applies, the cell is re-selected. – Jonathan Cabrera Mar 12 '19 at 23:09
66

try this

NSIndexPath* selectedCellIndexPath= [NSIndexPath indexPathForRow:0 inSection:0];
[self tableView:tableViewList didSelectRowAtIndexPath:selectedCellIndexPath];
[tableViewList selectRowAtIndexPath:selectedCellIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];

OR

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (/* should be selected */) {
        [cell setSelected:YES animated:NO];
    }
}

Swift 4 version is

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    func shouldSelect() -> Bool {
        // Some checks
    }
    if shouldSelect() {
            tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
    }
}
Onur Tuna
  • 1,030
  • 1
  • 10
  • 28
Ravindhiran
  • 5,304
  • 9
  • 50
  • 82
  • i used willDisplayCell and worked as expected, thanks. – ANeme Dec 12 '18 at 21:32
  • 1
    willDisplayCell work wrong if you scroll your tableview – Roman Filippov Feb 01 '19 at 15:13
  • you have to take into account that `willDisplay` delegate function will be called every time a cell will display, which might not be the desired behavior if you just want to set the selection once initially – Wizard Nov 19 '21 at 18:53
49

Swift 4: Selecting cell initially

import UIKit

class MyViewController: UIViewController, UITableViewDelegate {
    @IBOutlet weak var tableView: UITableView!
    ...

     func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

        if /* your code here */ == indexPath.row {
            tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableView.ScrollPosition.none)
        }
    }

    ...
}
Peter Kreinz
  • 7,979
  • 1
  • 64
  • 49
  • 4
    This should be the selected answer since it preserves selection when another row is selected. Maybe add a check in that if statement to make sure it only selects the row if it isn't current;y selected tho. – Douglas Cobb Feb 21 '17 at 20:47
  • 3
    I agree with Douglas. This should be marked as correct answer. I've been looking for solution in past couple of hours and this resolve my problem. – KMC Aug 01 '17 at 03:13
  • @user30646 same here!..this should be the correct answer as it helped me resolved my problem also! – Pangu Oct 07 '17 at 23:25
  • This tends to work, but worth noticing, that interferes with other table view animations. Your table view might jump or scroll wrongly. – gklka Nov 21 '17 at 10:52
  • This is the corrent answer. Thank you – Cristian Holdunu Mar 10 '18 at 20:32
  • Suggest editing answer to reflect that this is also the correct answer for. Swift 4 – HenryRootTwo Feb 07 '19 at 22:31
  • This works fine until the user manually deselects one of the preselected ones, scrolls to move the cell out of the view and scrolls again to move the cell into the view. `tableView(_:willDisplay:forRowAt:)` is called again and because the condition still applies, it's then re-selected – Jonathan Cabrera Mar 12 '19 at 22:05
10

I don't know you are expecting this answer. By default if you wants to select first row in your tableView with out manual selection, just initiate the didSelectRowAtIndexPath method like this

- (void)viewDidAppear:(BOOL)animated {
    NSIndexPath *indexPath=[NSIndexPath indexPathForRow:0 inSection:0];
    [myTableView selectRowAtIndexPath:indexPath animated:YES  scrollPosition:UITableViewScrollPositionBottom];
}
wesley
  • 859
  • 5
  • 15
  • It might be better to do it in `viewWillAppear` and with `animated:NO` so that the user doesn't see the cell being selected after the table view is shown. – Jonathan Cabrera Mar 12 '19 at 23:01
8

Best Option

Swift 5

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if shouldSelectThisRow {
        tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
    } else {
        tableView.deselectRow(at: indexPath, animated: false)
    }
}
Community
  • 1
  • 1
Lal Krishna
  • 15,485
  • 6
  • 64
  • 84
5

It will also help in the case of POP Navigation

.h file,

NSIndexPath *defaultSelectedCell

.m File

Put this code in ViewDidLoad

defaultSelectedCell= [NSIndexPath indexPathForRow:0 inSection:0];

in viewDidAppear

[self.tableView selectRowAtIndexPath:defaultSelectedCell animated:NO  scrollPosition:UITableViewScrollPositionNone];

This will help when you POP, Put this code in viewWillAppear

[self.catTableView selectRowAtIndexPath:defaultSelectedCell animated:NO scrollPosition:UITableViewScrollPositionNone];

in tableView didSelectRowAtIndexPath Method,

defaultSelectedCell = [NSIndexPath indexPathForRow:indexPath.row inSection:0];

This helps me perfectly either first time loading or Pop Navigation.

itsji10dra
  • 4,603
  • 3
  • 39
  • 59
Ashu
  • 3,373
  • 38
  • 34
2

Swift 4 and 5: Selecting cells initially just one time without willDisplayCell resetting their status:

override func viewWillAppear(_ animated: Bool) {
    for section in 0..<tableView.numberOfSections {
        for row in 0..<tableView.numberOfRows(inSection: section) {
            let indexPath = IndexPath(row: row, section: section)

            if /* your condition for selecting here */ {
                _ = tableView.delegate?.tableView?(tableView, willSelectRowAt: indexPath) // remove if you don't want to inform the delegate
                tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
                tableView.delegate?.tableView?(tableView, didSelectRowAt: indexPath) // remove if you don't want to inform the delegate
            }
        }
    }
}
Jonathan Cabrera
  • 1,656
  • 1
  • 19
  • 20
1

Swift 5, Thanks @Ravindhiran's answer a lot:

let selectedCellIndexPath = IndexPath(row: 0, section: 0)
tableView(tableViewList, didSelectRowAt: selectedCellIndexPath)
tableViewList.selectRow(at: selectedCellIndexPath, animated: false, scrollPosition: .none)

or

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if /* should be selected */{
          cell.setSelected(true, animated: false)
     }
}
dengST30
  • 3,643
  • 24
  • 25
1

Swift 5.2

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

        super.setSelected(selected, animated: true)
        accessoryType = selected ? .checkmark : .none
        backgroundColor = selected ? UIColor.secondarySystemBackground : UIColor.systemBackground
        myLabel.textColor = selected ? .tertiaryLabel : .secondaryLabel
        self.isUserInteractionEnabled = selected ? false : true

    }

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

        let cell = cell as! CustomTableViewCell

        if (/* condition is met */) {
            cell.setSelected(true, animated: true)

        }
    }

Tech UK
  • 37
  • 6
0
 func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        // reset cell state
        cell.accessoryType = .none  // (if you needed)
        tableView.deselectRow(at: indexPath, animated: false)

        // if need select
        cell.accessoryType = .checkmark // (if you needed)
        tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
    }

// method work fine when tap cell 
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath)

use [cell setSelected:YES animated:NO]; cell tap didSelectRowAt, didDeselectRowAt not work

yuelong qin
  • 139
  • 1
  • 4
0

I was struggling for over 5 hours for this initial selection. Using tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none) worked for me!

Thanks to everyone.

0

I add all the selected items from the cell into a selectedArray. In willDisplay, I am looking for the current element in the selectedArray. if I find it then

 tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)

In my code it`s looks like:

   func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let contact = sortedArray[indexPath.section].sectionObjects[indexPath.row]        
    for selectedContact in selectedContacts{
        if selectedContact == contact{
            tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
        }
    }
}

In this case both didSelectRowAt and didDeselectRowAt work well with preselected cells and newly selected cells, and no problem while scrolling table.

iGOR
  • 1
0

Swift 4.2 above versions supported:

Step 1: Select the cell in the storyboard and change the selection to none as below:

image of the table view cell needs to be selected initially on the launch

Step 2: In the custom table view cell class override the set selected function as below:

class menuTableViewCell: UITableViewCell {

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

// Configure the view for the selected state
    if selected {
        //below menuBarBgView is the view inside the cell
        self.menuBarBgView.backgroundColor = .white
        self.menuBarLabel.textColor = .yellow
        self.menuBarImage.isHidden = false
    }
    else {
        self.menuBarBgView.backgroundColor = .clear
        self.menuBarLabel.textColor = .black
        self.menuBarImage.isHidden = true
    }
 } }
Panky
  • 1
  • 2