79
votes

How do I, from a cell, get its indexPath in a UITableView?

I've searched around stack overflow and google, but all the information is on the other way around. Is there some way to access the superView/UITableView and then search for the cell?

More information about the context: there are two classes that I have, one is called Cue and one is called CueTableCell (which is a subclass of UITableViewCell) CueTableCell is the visual representation of Cue (both classes have pointers to each other). Cue objects are in a linked list and when the user performs a certain command, the visual representation (the CueTableCell) of the next Cue needs to be selected. So the Cue class calls the select method on the next Cue in the list, which retrieves the UITableView from the cell and calls its selectRowAtIndexPath:animated:scrollPosition:, for which it needs the indexPath of the UITableViewCell.

12
Just like your other question on getting the UITableView from a cell - why? This seems like a code smell of a bad design decision - there are not many legitimate use cases for a cell to know either it's indexPath or whether it is on the screen or not. - Paul.s
I agree with Paul.s's comment. You should indicate why you want to do this, because what you're trying to do is probably a bad idea. - rdelmar
@Paul.s I posted the design of my program in the question. It may be bad programming practice, in which case if you could suggest an alternative, that would be very helpful. I'm very new to cocoa and relatively new to programming in general. - sinθ
It's worth noting that indexPathsForVisibleRows exists - this is critical when you want to know "where you are" in a scroll. - Fattie

12 Answers

141
votes
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];

It helps reading the UITableView documentation, even if this is by some regarded to be controversial (see comments below).

The cell has no business knowing what its index path is. The controller should contain any code that manipulates UI elements based on the data model or that modifies data based on UI interactions. (See MVC.)

25
votes

Try with this from your UITableViewCell:

NSIndexPath *indexPath = [(UITableView *)self.superview indexPathForCell: self];

EDIT: Seems this doesn't work on iOS 8.1.2 and further. Thanks to Chris Prince for pointing it.

11
votes
  1. Put a weak tableView property in cell's .h file like:

    @property (weak,nonatomic)UITableView *tableView;

  2. Assign the property in cellForRowAtIndex method like:

    cell.tableView = tableView;

  3. Now wherever you need the indexPath of cell:

    NSIndexPath *indexPath = [cell.tableView indexPathForCell:cell];

11
votes

You can try this simple tip:

on your UITableViewCell Class :

@property NSInteger myCellIndex; //or add directly your indexPath 

and on your cellForRowAtIndexPath method :

...
[cell setMyCellIndex : indexPath.row]

now, you can retrieve your cell index anywhere

10
votes

To address those who say "this is a bad idea", in my case, my need for this is that I have a button on my UITableViewCell that, when pressed, is a segue to another view. Since this is not a selection on the cell itself, [self.tableView indexPathForSelectedRow] does not work.

This leaves me two options:

  1. Store the object that I need to pass into the view in the table cell itself. While this would work, it would defeat the point of me having an NSFetchedResultsController because I do not want to store all the objects in memory, especially if the table is long.
  2. Retrieve the item from the fetch controller using the index path. Yes, it seems ugly that I have to go figure out the NSIndexPath by a hack, but it's ultimately less expensive than storing objects in memory.

indexPathForCell: is the correct method to use, but here's how I would do it (this code is assumed to be implemented in a subclass of UITableViewCell:

// uses the indexPathForCell to return the indexPath for itself
- (NSIndexPath *)getIndexPath {
    return [[self getTableView] indexPathForCell:self];
}

// retrieve the table view from self   
- (UITableView *)getTableView {
    // get the superview of this class, note the camel-case V to differentiate
    // from the class' superview property.
    UIView *superView = self.superview;

    /*
      check to see that *superView != nil* (if it is then we've walked up the
      entire chain of views without finding a UITableView object) and whether
      the superView is a UITableView.
    */
    while (superView && ![superView isKindOfClass:[UITableView class]]) {
        superView = superView.superview;
    }

    // if superView != nil, then it means we found the UITableView that contains
    // the cell.
    if (superView) {
        // cast the object and return
        return (UITableView *)superView;
    }

    // we did not find any UITableView
    return nil;
}

P.S. My real code does access all this from the table view, but I'm giving an example of why someone might want to do something like this in the table cell directly.

7
votes

For swift

let indexPath :NSIndexPath? = (self.superview.superview as! UITableView)?.indexPathForCell(self)
4
votes

The answer to this question actually helped me a lot.

I used NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];

The sender in my case was a UITableViewCell and comes from the prepareForSegue method.

I used this because I did not have a TableViewControllerbut i had UITableView property outlet

I needed to find out the title of the Cell and hence needed to know the indexPath of it.

Hope this helps anyone!

4
votes

On iOS 11.0, you can use UITableView.indexPathForRow(at point: CGPoint) -> IndexPath?:

if let indexPath = tableView.indexPathForRow(at: cell.center) {
    tableView.selectRow
    (at: indexPath, animated: true, scrollPosition: .middle)
}

It gets the center point of the cell and then the tableView returns the indexPath corresponding to that point.

1
votes

try this (it only works if the tableView has only one section and all cells has equal heights) :

//get current cell rectangle relative to its superview
CGRect r = [self convertRect:self.frame toView:self.superview];

//get cell index
int index = r.origin.y / r.size.height;
1
votes

Swift 3.0 and above-

If one needs to get the indexpath from within a custom cell-

if let tableView = self.superview as? UITableView{
    if let indexPath = tableView.indexPath(for: self){
        print("Indexpath acquired.")
    }else{
        print("Indexpath could not be acquired from tableview.")
    }
}else{
    print("Superview couldn't be cast as tableview")
}

It's good practice is to look out for the failure cases.

0
votes

Try with this from your UITableViewCell:

NSIndexPath *indexPath = [(UITableView *)self.superview.superview indexPathForCell:self];
-1
votes

Swift 4.x

To provide access to my cell, i did something like this:

I have an extension of the UIView:

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

And then.. in my cell Class:

class SomeCell: UITableViewCell {

    func someMethod() {
        if let myViewController = self.parentViewController as? CarteleraTableViewController {
            let indexPath : IndexPath = (myViewController.tableView).indexPath(for: self)!
            print(indexPath)
        }
    }
}

Thats all form me.

Best regards.