xcodeuitableviewuikitsegueindexpath

tableView.indexPathForSelectedRow returns nil - Xcode 13.4.1 UIKit


please help me to understand why the tableView.indexPathForSelectedRow method returns nil.

I want to make a transfer from Table View Controller to View Controller. I have a segue by a StoryBoard and leadingSwipeActions.

import UIKit

class TableViewController: UITableViewController {
    
    let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]    


    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {            
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {            
        return months.count
    }

    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        cell.textLabel?.text = months[indexPath.row]

        return cell
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        if segue.identifier == "editItem" {
            let path = tableView.indexPathForSelectedRow
            print("\(path)") ##// Prints nil.##

        }
    } 

override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        let editAction = UIContextualAction(style: .normal, title: "Edit Item") { [self] (action, view, completion) in               
            
            
            performSegue(withIdentifier: "editItem", sender: self)
        }
        
        editAction.backgroundColor = .systemOrange
        
        return UISwipeActionsConfiguration(actions: [editAction])
    }


}

Solution

  • Leading swipe - and subsequent tap of the action button - does not select the row.

    Since the sender parameter of the "prepare for segue" method is defined as Any?, one approach would be to pass the indexPath when you call the segue:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    
        if segue.identifier == "editItem" {
            // make sure an Index Path was passed as the sender
            if let path = sender as? IndexPath {
                print("Index Path: \(path)")
            }
        }
    
    }
    
    override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        // note: use [weak self] to avoid possible retain cycle
        let editAction = UIContextualAction(style: .normal, title: "Edit Item") { [weak self] (action, view, completion) in
    
            guard let self = self else { return }
            
            // pass the path as the sender
            self.performSegue(withIdentifier: "editItem", sender: indexPath)
    
        }
        
        editAction.backgroundColor = .systemOrange
        
        return UISwipeActionsConfiguration(actions: [editAction])
    }
    

    Edit - in response to comment

    The sender: Any? parameter means that sender can be any object. That allows you to differentiate what action or piece of code initiated the segue.

    Examples:

    self.performSegue(withIdentifier: "editItem", sender: indexPath)
    self.performSegue(withIdentifier: "editItem", sender: 1)
    self.performSegue(withIdentifier: "editItem", sender: 2)
    self.performSegue(withIdentifier: "editItem", sender: "Hello")
    self.performSegue(withIdentifier: "editItem", sender: self)
    self.performSegue(withIdentifier: "editItem", sender: myButton)
    

    Then, in prepare(...), you can evaluate the sender to decide what to do next.

    Quite often, when using table views to "push" to another controller that relates to the tapped cell (a "details" controller, for example), the developer will connect a segue from the cell prototype to Details view controller. Then (if you gave that segue an identifier of "fromCell"), you can do something like this:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    
        if segue.identifier == "fromCell" {
            if let cell = sender as? UITableViewCell {
                if let path = tableView.indexPath(for: cell) {
                    print("Index Path: \(path)")
                    // here we might get data for that row/section,
                    //  and pass it to the next controller
                }
            }
        }
        
    }
    

    In your case, we send the indexPath of the cell we just acted on as the sender parameter.