2

I have a UITableView in which I have added a UIButton as accessory view for each cell. Note that I set the tag of the button as current row for future use.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // Configure the cell...

    if(cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];

        cellButton = [UIButton buttonWithType:UIButtonTypeCustom];
        cellButton.frame = CGRectMake(0, 0, 30, 30);  

        [cellButton setBackgroundImage:[UIImage imageNamed:@"cellButton.png"] forState:UIControlStateNormal];        
        [cellButton addTarget:self action:@selector(cellButtonAction:) forControlEvents:UIControlEventTouchUpInside];


        cellButton.tag = indexPath.row;  // <= Will use this in the next method
        cell.accessoryView = cellButton;
    }


    //Load cell with row based data

    return cell;
}

Now when one of these buttons is tapped, I need to make changes to the cell. So I implement cellButtonAction where I use the tag to get back the cell:

-(void)editCommentButtonAction:(id)sender
{
    UIButton *button = sender;
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:button.tag inSection:0];
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
    [self makeChangesToCell:cell];  
}

But this seems like a very round about way. Is there a cleaner way to do this?

gigahari
  • 671
  • 1
  • 7
  • 19
  • possible duplicate of [Detecting which UIButton was pressed in a UITableView](http://stackoverflow.com/questions/1802707/detecting-which-uibutton-was-pressed-in-a-uitableview) – Vladimir Jul 26 '12 at 12:49
  • No cleaner way - its how we all do it. BTW move this line "cellButton.tag = indexPath.row;" out of the if(cell == nil) so its always done (otherwise the tag will never update when you recycle). – David H Jul 26 '12 at 12:57
  • @DavidH it isn't how we all do it, tags suck. See my answer [here](http://stackoverflow.com/questions/9274494/how-to-know-the-uitableview-row-number/9274863#9274863) for, IMHO, a far superior method. – jrturton Jul 26 '12 at 13:06
  • @jrturton you made a good point - another possible superior way to do it. That said, I personally tend to use tags for all kinds of things - to find the particular label to say update a price etc. So I'm compfortable doing this. As everyone says, YMMV :-) – David H Jul 26 '12 at 13:13
  • jrturton, good idea! @DavidH Have brought the assignment out. But what would you do, if there were many sections? This scheme can't work then! – gigahari Jul 26 '12 at 13:22
  • Well, it can - you need to encode the section and row into one 32 bit field. You can create a macro that encodes (ors, shifts, etc) to encode and decode. In my case I never had button like this in more than one section so it was not a problem. – David H Jul 26 '12 at 13:29
  • @DavidH Yes I am aware of this approach, but so much computation, just to get to a variable in my own app seems too much! – gigahari Jul 26 '12 at 13:53

6 Answers6

5

So assuming that the button is in the contentView directly:

  • ask "sender" (ie the button) for its superview, which is the cell's contentView

  • ask that view for its superView, which is the cell

  • ask the tabview for the index of this cell:

- (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell

EDIT: Actually, I use a general purpose method or function now that just walks up the superviews, looking for a view that is 'KindOf' a UITableViewCell or a UICollectionViewCell. Works like a champ!

The code in Swift:

func containingUITableViewCell(tableView: UITableView, var view: UIView) -> (UITableViewCell, NSIndexPath)? {
while let v = view.superview {
    view = v
    if view.isKindOfClass(UITableViewCell.self) {
        if let cell = view as? UITableViewCell, let indexPath = tableView.indexPathForCell(cell) {
            return (cell, indexPath)
        } else {
            return nil
        }
    }
}
return nil

}

func containingUICollectionViewCell(collectionView: UICollectionView, var view: UIView) -> (UICollectionViewCell, NSIndexPath)? {
    while let v = view.superview {
        view = v
        if view.isKindOfClass(UICollectionViewCell.self) {
            if let cell = view as? UICollectionViewCell, let indexPath = collectionView.indexPathForCell(cell) {
                return (cell, indexPath)
            } else {
                return nil
            }
        }
    }
    return nil
}
David H
  • 40,852
  • 12
  • 92
  • 138
2

You can do it in a easier way. You will get the table view cell using the sender parameter. Check the following code.

-(void)editCommentButtonAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    UITableViewCell *cell = (UITableViewCell*)[button superview];
    [self makeChangesToCell:cell];  
}

Here,

  1. You are casting the sender of type id to a UIButton
  2. You are calling the getter superview of that button, it will give you the UITableViewCell
  3. Doing your customization.
Midhun MP
  • 103,496
  • 31
  • 153
  • 200
2

You can get the cell as follows.

    -(void)editCommentButtonAction:(id)sender
    {
        NSIndexPath* indexPath = 0;

        //Convert the bounds origin (0,0) to the tableView coordinate system
        CGPoint localPoint = [self.tableView convertPoint:CGPointZero fromView:sender];

        //Use the point to get the indexPath from the tableView itself.
        indexPath = [self.tableView indexPathForRowAtPoint:localPoint];
        //Here is the indexPath
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
        [self makeChangesToCell:cell];  
    }
Nishchith
  • 423
  • 5
  • 6
1

I had the same situation, the thing is that I had an imageView inside my tablecells, and I want to get the cell that holds the imageview that I tapped..

//MyCell is subclass of UITableViewCell

    if ([[[[sender view] superview] superview] isKindOfClass:[MyCell class]]) {
        MyCell *cell = (MyCell *)[[[sender view] superview] superview];
        NSIndexPath *cellIndexPath = [myTable indexPathForCell:cell];
        NSLog(@"cellIndexPath: %@  - %@",cellIndexPath, [videoURLArray objectAtIndex:cellIndexPath.row]);

    }
  • [sender view] - imageView
  • [[sender view] superview] -- where my imageView was placed (in this case, the superview of my imageView is the cell's contentView)
  • [[[sender view] superview] superview] --- where the contentView was placed -- the cell

The NSLog part should print the correct row and section of the tapped imageView.. Just Modify the code. ;)

JCurativo
  • 713
  • 6
  • 17
1
Hello gigahari Use the following code.  

- (void)cellStopDownloadButtonClicked:(id)sender 
{
id viewType = [sender superview];
do {
viewType = [viewType superview];
}
while (![viewType isKindOfClass:[CustomCell class]] || !viewType);
CustomCell *cell = (CustomCell *)viewType;
// use the cell
}

This will work in all cases like ios 6 and ios 7. in ios 7 an extra view added in cell (content view).

if you use [[sender superview] superview] it will fail in some cases.

Vijay
  • 997
  • 1
  • 12
  • 27
0

The way i usually do this:

  • cell stores the data for the given row or the index path
  • create a protocol with a method -didSelectSomething or -didSelectAtIndexPath:
  • the cell holds a reference to an instance of the protocol, which will be your datasource
  • wire the button action to the cell in your nib
  • have the cell call the delegate
  • DON'T FORGET to clean up the cell in prepareForReuse. Storing state in cells can lead to nasty bugs, so be sure to clean up on reuse.

The tag thing is a real hack, and it won't work if you have more than one section. The sender superview will break if you reorder the views in your nib.

For this particular case (accessory view), isn't there a dedicated table delegate method?

kra
  • 214
  • 2
  • 9