How to get indexPath in cell class?

I'm trying to get the indexPath of a tableView cell in the cell class.

I've got a collectionView inside the table view cells and I'm trying to make label inside the collectionView cell show the indexPath.row of the tableView cell that particular collectionView in.


Currebtly i have

var indexPathForCell: indexPath

in my cell class.


Then in the tableView class i have this in cellForRowAt indexpath

cell.indexpathForCell = indexPath
lbl.text = String(indexPathForCell.row)


If there is "3" or fewer tableView cells this works but if theres more then the 4th row then shows "0" as the indexPathForcell.row, and as I scroll in the collectionView the numbers then chnage from "0" to "3" and even show "1". Each cell then shows a different number as i scroll.

Why do you declare a var indexPathForCell ?


in the override, you should have something like this :

- IdForCell is the identifier for the cells


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

        // Configure the cell...
        cell.textLabel!.text = String(indexPath.row)

       return cell
    }

I know how to get the indexPath.row there. What I’m trying to do is get the indexPath.row in cell.swift not tableViewController.swift

Why do you want to get it in cell.swift ? To display the row in the label of the cell (that's what your code shows) ?


What you could do:

- create a row property in cell.swift

     var row : Int = -1

- assign the row in tableViewController

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

        // Configure the cell...
        cell.textLabel!.text = String(indexPath.row)
        cell.row = indexPath.row
       return cell
    }


Is that what you need ?

A cell may be used for different rows at differ times during the lifetime of the table view, so you can't permanently set the path into the cell. Further, cells may exist but not correspond to a row (until they're later used). Conversely, not all rows have a cell — the only rows that are guaranteed to have a cell at any given moment are those that are visible in the table view.


That means you would have to set the path into the cell at the moment when the cell becomes used for the path, which is in "tableView(_:cellForRowAt:)" — as Claude told you.


In other words, what matters is when you get the path, not where.

I think maybe you're missing the part where he said


> I've got a collectionView inside the table view cells


He's trying to get the *table* view row in the *collection* view cells I think. In any case, the strategy is the same at both levels. The view controller sets a property on the cell when it sets up the cell, passing it whatever bits of the model the cell needs to know. The middle level, whatever object is setting up the collection view cells, would pass on that property's value to the collection view cell.

I’ve got tabs at the top with categories, each category displays a tableView with a collectionView in each cell. Each tableView cell is a subcategory. The tableView indexPath.row tells the collectionView which subcategory to display and the collectionView indexPath.row tells the collectionView cell which restaurant data to load from Firebase. That’s why I‘d needed the indexPath.row for the tableView cell.


Currently in Firebase I have restaurant data following this format 0-1-2 (eg) ”0” is the category number, the top bar gives this as an index Int, the “1” is the subcategory num which would be the indexPath.row of the tableView, then “2” is the restaurant number which is the indexPath.row of the collectionView. Using this method made it easier to keep track of everything (I’m not sure on the correct way on doing something like this) What would be a better way of going about this?

Yes, I agree this all sounds just fine. You just have to realize that a particular table view cell object might represent different subcategories at different times. For example, the cell for row/subcategory 0 is at the top of the table initially. After you scroll down far enough so that row 0 is no longer visible, that same cell might be re-used for a different row, say row 5. That's why your scrolling gives incorrect results — row 5 will say it's row subcategory 0, because that what the cell used to be.


Again, you cannot store the index path in a table view cell permanently. You must re-set that property every time the cell is re-used. The correct time to do that is in your table view's "tableView(_:cellForRowAt:)" delegate method, right after you "dequeueReusableCell(withIdentifier:for:)" to get the cell for an indexPath. Right there, you can do this (from your original code fragment):


cell.indexPathForCell = indexPath


Now, any table cell knows its own index path. The next step is that your collection view cell needs to be able to find its table view cell, in order to be able to find out its row/subcategory. You don't give us enough information about your collection view to suggest an exact way of doing this, but it's going to need to be similar to the above: whenever you dequeue a collection view cell, you should set its category/subcategory/restaurant numbers, since collection view cells can be reused the same way table view cells can.

Thanks for clearing up on how the row number changes in the tableView. I tried setting label in the cell outside the collectionView

lbl.text = String(indexPathForCell.row)

and this displays the correct row number.

The label inside the collectionView cell howver still shows the wrong numbers as you scroll. I'm assuming this is beacuase how it loads data as you explained, but I'm still not sure how to solve this. here's my cell class with the collectionView.


class AdSelectCell: UITableViewCell , UICollectionViewDataSource, UICollectionViewDelegate {

    @IBOutlet private weak var collectionView: UICollectionView!

    var indexPathForCell: IndexPath?


    let TV = TableViewController()
   let colours = [UIColor.red, UIColor.blue, UIColor.orange, UIColor.darkGray, UIColor.green]

    override func awakeFromNib() {
        super.awakeFromNib()
  
        collectionView.dataSource = self as? UICollectionViewDataSource
        collectionView.delegate = self as? UICollectionViewDelegate
  
    }



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



    func numberOfSections(in collectionView: UICollectionView) -> Int {
    
        return 1
    }



    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    
    
        return colours.count
    }



    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "adCVcell", for: indexPath)
    
        let subCat = indexPathForCell!.row
           
        let pic = cell.viewWithTag(1) as! UIImageView
        let rateView = cell.viewWithTag(2) as! UIView
        let rateLbl = cell.viewWithTag(3) as! UILabel
        let title = cell.viewWithTag(4) as! UILabel
        let subTitle = cell.viewWithTag(5) as! UILabel
        let distView = cell.viewWithTag(6) as! UIView
        let distLbl = cell.viewWithTag(7) as! UILabel
    
       title.text = String(subCat)

        pic.backgroundColor = colours[indexPath.row]
    
    
        return cell
    }


}

Surely it's just:


       title.text = String(indexPath.row)


isn't it? (And you don't need this "indexPathForCell" at all. The one in the table cell view existed solely to pass on to the collection view.) Or am I missing something?


I'm also not not sure where "lbl" is or what it's for. Do you even need it?

if I just use

title.text = String(indexPath.row)

that’ll give me the indexPath.row of the collectionView not the tableViewCell.

indexPathForCell is set in the tableView rowForItemAt indexPath:

//let cell = dequeue.... here
cell.indexPathForCell = indexPath


lbl I’d created then removed. It was a label created outside the tableView just to check if the values were correct outside the collectionView.

OIC, it's hard to keep of track of what level we're talking about, and I thought you had already solved this particular subproblem: how to find the table view cell from the collection view delegate. This isn't hard, but it's just a bit awkward.


The easiest way might to run up the view hierarchy from the collection view until you find the table cell view (which is probably only 1 or 2 steps). Once you have the table cell, you know what to do.


An alternative approach might be to subclass UICollectionView and add a "subcategory" property there (instead of the "indexPathForCell" in the table view cell), but I don't think it'd be any simpler in the end.


Incidentally, wouldn't this be easier if you used multiple sections within a single collection view, rather than multiple collection views within a table view?

I can’t use multiple sections in the collectionView because each subcategory scrolls horizontally. The height is fixed per subcategory so it takes up less space and quicker to navigate.

What do u mean by run up the view hierarchy from the collectionView till I find the tableView cell?

I mean, start from the collectionView, and follow the "superview" references to traverse the view hierarchy, something like this:


var parentView: UIView! = collectionView
while parentView != nil {
     if let tableCell = parentView as? AdSelectCell {
          title.text = String (tableCell.indexPathForCell!.row)
          break
     }
     parentView = parentView.superview
}


Or if you know (and require) that the collection view is a direct subview of the table cell, you can do without the loop:


if let tableCell = collectionView.superview as? AdSelectCell {
     title.text = String (tableCell.indexPathForCell!.row)
}

> What do u mean by run up the view hierarchy from the collectionView till I find the tableView cell?


I would strongly recommend avoiding this kind of approach. How the view hierarchy is organized (outside of your own views) is an internal implementation detail of UIKit and is subject to change in different iOS versions. So any code you write that depends on it may break without warning.


I think your main issue might be that you're not telling the collection view to reloadData() from the data source when the cell is reused. Perhaps you could do that in the cell's prepareForReuse method, or maybe in a didSet { } on the indexPathForCell property.


Or perhaps it's a typo such as "sub" vs "subCat" in your posted code. Since you've posted an incomplete sample that won't compile, it's hard to tell.

>> How the view hierarchy is organized (outside of your own views) is an internal implementation detail of UIKit


This is a reasonable point in general, but in this case, the collection view is a descendant of the table cell view, because that is the relationship set up in IB when designing the table cell. It's possible there's a private view in the hierarchy between the collection view and the table cell, so the looping version of the code might be safer, but it's certain that there is an ancestor relationship there.


Except … the code isn't actually necessary, now that I look properly at the rest of the code posted. The table cell is the the collection view delegate, so the original line of code (line 47):


        let subCat = indexPathForCell!.row


was correct.


>> your main issue might be that you're not telling the collection view to reloadData() from the data source


Yes, that's why it's not working. (I made a test project so I could see what was really going on.)

How to get indexPath in cell class?
 
 
Q