lilbiscuit
lilbiscuit

Reputation: 2249

Select table cell programmatically in Swift

In Swift, I can select a table cell like this:

 let indexPath = IndexPath(row: 0, section: 0);
 tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none)

But trying to call didSelectRowAt like this:

tableView.delegate?.tableView!(tableView, didSelectRowAt: indexPath)

Which fires this:

 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = categoryListView.cellForRow(at: indexPath) as! CategoryCellView   // error here

    //do other stuff
}

and causes this error at //error here above: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

I am using this class for the CategoryCellView which is a separate nib:

@IBDesignable
class CategoryCellView: UITableViewCell {
    @IBOutlet weak var myCategoryView: Category!
    var categoryId: Int?
    var index: Int?
}

How can I trigger didSelectRowAt here?

Upvotes: 0

Views: 859

Answers (2)

DonMag
DonMag

Reputation: 77442

You're close...

Give this a try:

    let indexPath = IndexPath(row: 0, section: 0);
    tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
    self.tableView(tableView, didSelectRowAt: indexPath)


Edit

As noted (and this can be found in many places), one should never call delegate methods containing will, did and should yourself.

Another approach might be:

@objc func simulateSelectRow() -> Void {
    print("sim")
    let indexPath = IndexPath(row: 0, section: 0);
    tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
    myDidSelectRowAt(tableView, indexPath: indexPath)
}

func myDidSelectRowAt(_ tableView: UITableView, indexPath: IndexPath) -> Void {
    // do what you want when row is selected
    print("Row:", indexPath.row, "in Section:", indexPath.section, "was selected.")
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    myDidSelectRowAt(tableView, indexPath: indexPath)
}

This may, or may not, be suitable for your needs - but it could be an option.

Upvotes: 0

Mike Mertsock
Mike Mertsock

Reputation: 12015

cellForRow(at: indexPath) can return nil if the cell is not visible — and the cell might not be visible immediately after calling selectRow. So I would recommend updating your didSelectRow implementation to avoid the as! force-unwrap and be more defensive:

if let cell = categoryListView.cellForRow(at: indexPath) as? CategoryCellView {
    // do stuff with the cell
}

(or maybe use guard let if that makes more sense)

I would recommend not directly invoking UITableView or UITableViewDelegate functions such as didSelectRow (or other UIKit standard delegate functions) from your own code, as it breaks the original design contract of these functions. This can cause buggy or unwanted behavior if you're calling didSelectRow directly on the UITableView object. Instead, write another custom function in your class that implements UITableViewDelegate where you can updating the appropriate UITableViewCell after calling selectRow. You could possibly call this same function from your didSelectRow implementation and thus reuse some code.

Depending on what you need to do with the cell in your didSelectRow method, you could address the programmatic-selection scenario in other ways. For example:

  • Within your cellForRowAt implementation, you can check whether indexPath == tableView.indexPathForSelectedRow, or check tableView.indexPathsForSelectedRows?.contains(indexPath) ?? false, to determine if the cell about to be displayed is a selected cell. Note that at this moment, the cell's isSelected value may be false. The UITableView will set isSelected to true if needed immediately afterward. Which leads to the second option:
  • Implement override func setSelected(_ selected: Bool, animated: Bool) in your CategoryCellView, if you just need to update the appearance of that specific table view cell. If the cell is already visible when you call selectRow, setSelected will be called immediately. If the cell is not visible at the time you call selectRow, setSelected will be called on that cell later, immediately after the delegate's tableView(_:,cellForRowAt:) is called, when the user scrolls to that cell

Upvotes: 1

Related Questions