Get the indexPath of a cell with delegates/protocols?

Hello,

I'm rather new to the idea of delegates and protocols, and I'm having some trouble using them to get the indexPath of a custom UITableViewCell I've made. I have a button in each cell, and when I tap on a cell's button I need to get that cell's indexPath (to be able to access that cell's data model). Any help would be greatly appreciated! Please let me know if you need to see my code.

Thanks,

jimothy177

Please let me know if you need to see my code.

Yes, please. And please do not put a link to sort of file sharing services, Please show your code as text formatted with < >.

Did you try using


func indexPath(for: UITableViewCell) -> IndexPath?

Returns an index path representing the row and section of a given table-view cell.


To find the cell from the button, you could look for its superview.

Or call superviews until you reach a UITableViewCell.

Sorry for the late response. Here is my code, both the custom UITableViewCell I made and my TableViewController class. I don't know how to get the indexPath in the didChangeStatusColor method, and I can't figure out how to pass the cell to that function. I'm relatively new to Swift, so I'm sort of teaching myself right now. Thanks!


import UIKit

protocol ItemCellDelegate
{
    func didChangeStatusColor();
}

class ItemTableViewCell: UITableViewCell
{
    //MARK: Properties
    
    @IBOutlet var itemName: UILabel!
    @IBOutlet var itemDate: UILabel!
    @IBOutlet var itemStatusColor: UILabel!
    @IBOutlet var changeStatusColorFunc: UIButton!
    
    var delegate: ItemCellDelegate?
    
    @IBAction func changeStatusColorTapped(_ sender: UIButton)
    {
        let currentCell = self
        delegate?.didChangeStatusColor()
    }
    
    override func awakeFromNib()
    {
        super.awakeFromNib()
        // Initialization code
    }

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

        // Configure the view for the selected state
    }

}



import UIKit
import os.log

class ItemTableViewController: UITableViewController, ItemCellDelegate
{
    
    // MARK: Properties
    var items = [Item]()
    var date1 = Date.init()
    var date2 = Date.init()
    var date3 = Date.init()
    
    // MARK: Actions
    @IBAction func unwindToItemList(sender: UIStoryboardSegue)
    {
        if let sourceViewController = sender.source as? ItemEditViewController, let item = sourceViewController.item
        {
            if let selectedIndexPath = tableView.indexPathForSelectedRow
            {
                // Update an existing item
                items[selectedIndexPath.row] = item
                tableView.reloadRows(at: [selectedIndexPath], with: .none)
            }
            else
            {
                // Add a new item
                let newIndexPath = IndexPath(row: items.count, section: 0)
                items.append(item)
                tableView.insertRows(at: [newIndexPath], with: .automatic)
            }
            
            // Save the items
            saveItems()
        }
    }
    
    func didChangeStatusColor()
    {
        print("didChangeStatusColor tapped!")
    }
    
    // MARK: Private Methods
    private func loadSampleItems()
    {
        guard let item1 = Item.init(name: "Do the dishes", date: date1, statusColor: UIColor.blue, colorList: [UIColor.blue, UIColor.green, UIColor.yellow]) else
        {
            fatalError("Unable to instantiate item1")
        }
        guard let item2 = Item.init(name: "Feed the dogs", date: date2, statusColor: UIColor.yellow, colorList: []) else
        {
            fatalError("Unable to instantiate item2")
        }
        
        guard let item3 = Item.init(name: "Finish economics paper", date: date3, statusColor: UIColor.purple, colorList: [UIColor.orange, UIColor.green]) else
        {
            fatalError("Unable to instantiate item3")
        }
        
        items += [item1, item2, item3]
    }

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        // Use the edit button item provided by the table view controller
        navigationItem.leftBarButtonItem = editButtonItem
        
        // Load any saved items, otherwise load sample data
        if let savedItems = loadItems()
        {
            items += savedItems
        }
        else
        {
            // Load the sample items
            loadSampleItems()
        }
        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int
    {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        // #warning Incomplete implementation, return the number of rows
        return items.count
    }

    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        // Set the cell identifier to ItemTableViewCell
        let cellIdentifier = "ItemTableViewCell"
        
        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ItemTableViewCell else
        {
            fatalError("Unable to downcast the cell in cellForRowAt to ItemTableViewCell")
        }
        
        // Gets the appropriate item from the datasource (the array of items)
        let item = items[indexPath.row]
        
        // Now use that item to configure the ItemTableViewCell
        cell.itemName.text = item.name
        
        // set the tag for the changeStatusColor button
        //cell.changeStatusColorFunc.tag = indexPath.row;
        //cell.changeStatusColorFunc.addTarget
        
        cell.delegate = self
        
        // if the date is the empty flag, set the itemDate label to the empty string
        if item.date == Date.init(timeIntervalSince1970: 0)
        {
            cell.itemDate.text = ""
        }
        else    // otherwise, get the date in the new format and set it to the itemDate label
        {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = " MMM d"
            let stringFromDate = dateFormatter.string(from: item.date)
            cell.itemDate.text = stringFromDate
        }

        cell.itemStatusColor.backgroundColor = item.statusColor
        
        

        //cell.itemStatusColor.backgroundColor = item.statusColor

        // Configure the cell...

        return cell
    }
    

    
    // Override to support conditional editing of the table view.
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
    {
        // Return false if you do not want the specified item to be editable.
        return true
    }
    

    
    // Override to support editing the table view.
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
    {
        if editingStyle == .delete
        {
            // Delete the row from the data source
            items.remove(at: indexPath.row)
            saveItems()
            tableView.deleteRows(at: [indexPath], with: .fade)
        }
        else if editingStyle == .insert
        {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }    
    }
    

    /*
    // Override to support rearranging the table view.
    override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath)
    {

    }
    */

    /*
    // Override to support conditional rearranging of the table view.
    override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool
    {
        // Return false if you do not want the item to be re-orderable.
        return true
    }
    */

    
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?)
    {
        super.prepare(for: segue, sender: sender)
        
        switch(segue.identifier ?? "")
        {
        case "AddItem":
            os_log("Adding a new item.", log: OSLog.default, type: .debug)
        case "showDetail":
            guard let itemDetailViewController = segue.destination as? ItemEditViewController else
            {
                fatalError("Unexpected destination: \(segue.destination)")
            }
            guard let selectedItemCell = sender as? ItemTableViewCell else
            {
                fatalError("Unexpected sender: \(segue.destination)")
            }
            guard let indexPath = tableView.indexPath(for: selectedItemCell) else
            {
                fatalError("The selected cell is not being displayed in the table.")
            }
            
            let selectedItem = items[indexPath.row]
            itemDetailViewController.item = selectedItem
            
        default:
            fatalError("Unexpected segue identifier: \(String(describing: segue.identifier))")
        }
    }
    
    // MARK: Private Methods
    private func saveItems()
    {
        let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(items, toFile: Item.ArchiveURL.path)
        if isSuccessfulSave
        {
            os_log("Items saved successfully.", log: OSLog.default, type: .debug)
        }
        else
        {
            os_log("Failed to save meals", log: OSLog.default, type: .debug)
        }
    }
    
    private func loadItems() -> [Item]?
    {
        return NSKeyedUnarchiver.unarchiveObject(withFile: Item.ArchiveURL.path) as? [Item]
    }
}

Thanks for showing your code.


One simple and easy-to-understand way is havein `indexPath` as an instance property of your `ItemTableViewCell`.


Your protocol may need some modification like this:

protocol ItemCellDelegate: class {
    func didChangeStatusColor(for cell: ItemTableViewCell)
}


The delegate method need to pass some info about the cell, and `: class` is needed to make the protocol declaration as `weak`:

class ItemTableViewCell: UITableViewCell {
    //...
    
    weak var delegate: ItemCellDelegate? //<- Usually `delegate` is declared as `weak` to avoid reference cycle
    
    var indexPath: IndexPath? //<- Hold indexPath as an instance property
    
    @IBAction func changeStatusColorTapped(_ sender: UIButton) {
        let currentCell = self
        delegate?.didChangeStatusColor(for: currentCell) //<-Pass some info about the cell
    }
    
   //...
}


And you may need to adapt your `ItemTableViewController`:

class ItemTableViewController: UITableViewController, ItemCellDelegate {
    //...

    func didChangeStatusColor(for cell: ItemTableViewCell) {
        print("didChangeStatusColor tapped!")
        print("cell's indexPath=\(cell.indexPath!)")
    }

    //...

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //...


        //Set indexPath of the cell
        cell.indexPath = indexPath
        cell.delegate = self

        //...
    }

    //...
}


You have other optons, like using `cell.tag`, please try my code and do it in your own way.


---

By the way, you usually do not write `.init` explicitly for usual instantiation. `Date.init()` can be simply `Date()`.

Thank you so much! I edited my code using your suggestions and now it works. However, my end goal is to be able to change the cell's statusColor using that index stored in cell.indexPath. However, when I tap on the button, didChangeStatusColor prints something like this:


didChangeStatusColor tapped!

Cell's index path = [0, 0]

didChangeStatusColor tapped!

Cell's index path = [0, 1]

didChangeStatusColor tapped!

Cell's index path = [0, 2]


Is there a reason why I'm getting a pair of values instead of just the second value? Is the first number the section in the TableView and the second the index in that section? I'm only using one section right now, so that would make sense.

Seems you have guessed correctly. `IndexPath` (or `NSIndexPath`) represents a sequence of indexes, like file path represents a sequence of file or directory names.


And in case of working with `UITableView`, `IndexPath` represents `section` and `row`.

Get the indexPath of a cell with delegates/protocols?
 
 
Q