84

I have a tableview controller that displays a row of cells. Each cell has 3 buttons. I have numbered the tags for each cell to be 1,2,3. The problem is I don't know how to find on which cell a button is being pressed. I'm currently only getting the sender's tag when one of the buttons has been pressed. Is there a way to get the cell row number as well when a button is pressed?

ghr
  • 505
  • 4
  • 8
minimalpop
  • 6,997
  • 13
  • 68
  • 80

10 Answers10

277

You should really be using this method instead:

CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];

Swift version:

let buttonPosition = sender.convert(CGPoint(), to:tableView)
let indexPath = tableView.indexPathForRow(at:buttonPosition)

That will give you the indexPath based on the position of the button that was pressed. Then you'd just call cellForRowAtIndexPath if you need the cell or indexPath.row if you need the row number.

If you're paranoid, you can check for if (indexPath) ... before using it just in case the indexPath isn't found for that point on the table view.

All of the other answers are likely to break if Apple decides to change the view structure.

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
  • 8
    this, a million times. *none* of the others worked, but this was perfect and by far the simplest solution – ine Apr 29 '13 at 04:40
  • 2
    This answer works because it is **the correct** solution. The others are workarounds. Thanks! – Bruno Philipe Sep 08 '13 at 02:30
  • 1
    In case you _don't_ have **self.tableview**, please refer to my answer on this page. – Ashok Oct 18 '13 at 03:23
  • 1
    This worked for me once I realized my sender was the wrong class. Fixed it to be the UIButton pressed, and now it is working! – lordB8r Jun 24 '14 at 15:28
  • This is working, but the UIButton in the last row of the table will change text when any of the other UIButton are selected. Any advice for what I should check? – Luke Irvin Sep 09 '14 at 23:42
  • 1
    just remember the method is `convertPoint:*to*View` not `convertPoint:*from*View`. I used Xcode autocomplete and ended up with fromView method only leading to not working... – atastrophic Jul 13 '15 at 19:41
  • 1
    See answer below for swift 3. @iwasrobbed : feel free to incorporate my answer into yours. – Roy Falk Mar 16 '17 at 19:08
  • Works perfectly –  Mar 31 '17 at 10:14
  • Check this out : Simple, Quick & Easy. https://stackoverflow.com/a/55156685/5846076 – Mushrankhan Mar 14 '19 at 07:11
40

Edit: This answer is outdated. Please use this method instead


Try this:

-(void)button1Tapped:(id)sender
{
    UIButton *senderButton = (UIButton *)sender;
    UITableViewCell *buttonCell = (UITableViewCell *)[senderButton superview];
    UITableView* table = (UITableView *)[buttonCell superview];
    NSIndexPath* pathOfTheCell = [table indexPathForCell:buttonCell];
    NSInteger rowOfTheCell = [pathOfTheCell row];
    NSLog(@"rowofthecell %d", rowOfTheCell);
}

Edit: If you are using contentView, use this for buttonCell instead:

UITableViewCell *buttonCell = (UITableViewCell *)senderButton.superview.superview;
Community
  • 1
  • 1
user523234
  • 14,323
  • 10
  • 62
  • 102
  • 1
    You're not supposed to add subviews to the cell itself but only to its `contentView` property - so this solution doesn't really work. –  Sep 07 '12 at 15:45
  • 1
    I'm agreeing with @H2CO3 and I also don't think this should be done by referencing superviews. Please see a better way below: http://stackoverflow.com/a/16270198/308315 – iwasrobbed Apr 29 '13 at 02:00
  • @minimalpop can you please change the correct answer ? to have a better Q/A ! – Thomas Besnehard Feb 27 '14 at 09:54
  • This doesn't work in iOS 7. Use indexPathForRowAtPoint: answer below – Austin Mar 28 '14 at 01:26
  • I tried the same version with an UICollectionView and works perfectly. Thanks a lot! – Claus Mar 28 '14 at 17:33
  • @iWasRobbed improved his answer below to use `convertPoint` and `indexPathForRowAtPoint`. Please change the accepted answer, thank you! – Michael Osofsky Aug 10 '14 at 01:15
  • You can use this method, but you should have an intermediate step of getting the contentView from the button superview, then get the actual cell from the contentView superview – Adam Johns Mar 09 '15 at 16:21
18

I would recommend this way to fetch indexPath of cell which has any custom subview - (compatible with iOS 7 as well as all previous versions)

-(void)button1Tapped:(id)sender {
//- (void)cellSubviewTapped:(UIGestureRecognizer *)gestureRecognizer {
//    UIView *parentCell = gestureRecognizer.view.superview;
    UIView *parentCell = sender.superview;

    while (![parentCell isKindOfClass:[UITableViewCell class]]) {   // iOS 7 onwards the table cell hierachy has changed.
        parentCell = parentCell.superview;
    }

    UIView *parentView = parentCell.superview;

    while (![parentView isKindOfClass:[UITableView class]]) {   // iOS 7 onwards the table cell hierachy has changed.
        parentView = parentView.superview;
    }


    UITableView *tableView = (UITableView *)parentView;
    NSIndexPath *indexPath = [tableView indexPathForCell:(UITableViewCell *)parentCell];

    NSLog(@"indexPath = %@", indexPath);
}

This doesn't require self.tablview either.

Also, notice the commented code which is useful if you want the same through a @selector of UIGestureRecognizer added to your custom subview.

Ashok
  • 6,224
  • 2
  • 37
  • 55
3

There are two ways:

  1. @H2CO3 is right. You can do what @user523234 suggested, but with a small change, to respect the UITableViewCellContentView that should come in between the UIButton and the UITableViewCell. So to modify his code:

    - (IBAction)button1Tapped:(id)sender
    {
        UIButton *senderButton = (UIButton *)sender;
        UITableViewCellContentView *cellContentView = (UITableViewCellContentView *)senderButton.superview;
        UITableViewCell *tableViewCell = (UITableViewCell *)cellContentView.superview;
        UITableView* tableView = (UITableView *)tableViewCell.superview;
        NSIndexPath* pathOfTheCell = [tableView indexPathForCell:tableViewCell];
        NSInteger rowOfTheCell = pathOfTheCell.row;
        NSLog(@"rowofthecell %d", rowOfTheCell);
    }
    
  2. If you create a custom UITableViewCell (your own subclass), then you can simply call self in the IBAction. You can link the IBAction function to your button by using storyboard or programmatically when you set up the cell.

    - (IBAction)button1Tapped:(id)sender
    {
        UITableView* tableView = (UITableView *)self.superview;
        NSIndexPath* pathOfTheCell = [tableView indexPathForCell:self];
        NSInteger rowOfTheCell = pathOfTheCell.row;
        NSLog(@"rowofthecell %d", rowOfTheCell);
    }
    
Mr. T
  • 12,795
  • 5
  • 39
  • 47
2

Another simple way:

  • Get the point of touch in tableView

  • Then get index path of cell at point

  • The index path contains row index

The code is:

- (void)buttonTapped:(id)sender {
    UITapGestureRecognizer *tap = (UITapGestureRecognizer *)sender;
    CGPoint point = [tap locationInView:theTableView];

    NSIndexPath *theIndexPath = [theTableView indexPathForRowAtPoint:point];

    NSInteger theRowIndex = theIndexPath.row;
    // do your stuff here
    // ...
}
vietstone
  • 8,784
  • 16
  • 52
  • 79
2

I assume you add buttons to cell in cellForRowAtIndexPath, then what I would do is to create a custom class subclass UIButton, add a tag called rowNumber, and append that data while you adding button to cell.

Derek Li
  • 3,089
  • 2
  • 25
  • 38
  • 3
    This is fine, but there's no reason to create a subclass of `UIButton` for it. `tag` is a common property of `UIView`. – Rob Napier Sep 21 '11 at 18:05
  • Don't know what was I thinking and assume **UI** something is not a **UI**, thanks for correction! – Derek Li Sep 21 '11 at 18:41
1

Swift 3

Note: This should really go in the accepted answer above, except that meta frowns upon such edits.

@IBAction func doSomething(_ sender: UIButton) {
   let buttonPosition = sender.convert(CGPoint(), to: tableView)
   let index = tableView.indexPathForRow(at: buttonPosition)
}

Two minor comments:

  1. The default function has sender type as Any, which doesn't have convert.
  2. CGPointZero can be replaced by CGPoint()
Community
  • 1
  • 1
Roy Falk
  • 1,685
  • 3
  • 19
  • 45
0

I would like to share code in swift -

extension UITableView
{
    func indexPathForCellContainingView(view1:UIView?)->NSIndexPath?
    {
        var view = view1;
        while view != nil {
            if (view?.isKindOfClass(UITableViewCell) == true)
            {
                return self.indexPathForCell(view as! UITableViewCell)!
            }
            else
            {
                view = view?.superview;
            }
        }
        return nil
    }
}
Anupam Mishra
  • 3,408
  • 5
  • 35
  • 63
0

In swift:

@IBAction func buttonAction(_ sender: UIButton) {
    guard let indexPath = tableView.indexPathForRow(at: sender.convert(CGPoint(), to: tableView)) else {
        return
    }
    // do something
}
Leszek Szary
  • 9,763
  • 4
  • 55
  • 62
0

One solution could be to check the tag of the button's superview or even higher in the view hierarchy (if the button is in the cell's content view).

Jakob W
  • 3,347
  • 19
  • 27