20

I currently have a UITableView that is populated with a custom UITableViewCell that is in a separate nib. In the cell, there are two buttons that are wired to actions in the custom cell class. When I click one of the buttons, I can call the proper method, but I need to know which row the button was pressed in. The tag property for each button is coming as 0. I don't need to know when the entire cell is selected, just when a particular button is pressed, so I need to know which row it is so I can update the proper object.

Tai Squared
  • 12,273
  • 24
  • 72
  • 82

10 Answers10

21

Much easier solution is to define your button callback with (id)sender and use that to dig out the table row index. Here's some sample code:

- (IBAction)buttonWasPressed:(id)sender
{
    NSIndexPath *indexPath =
        [self.myTableView
         indexPathForCell:(UITableViewCell *)[[sender superview] superview]];
    NSUInteger row = indexPath.row;

    // Do something with row index
}

Most likely you used that same row index to create/fill the cell, so it should be trivial to identify what your button should now do. No need to play with tags and try to keep them in order!

Clarification: if you currently use -(IBAction)buttonWasPressed; just redefine it as -(IBAction)buttonWasPressed:(id)sender; The additional callback argument is there, no need to do anything extra to get it. Also remember to reconnect your button to new callback in Interface Builder!

JOM
  • 8,139
  • 6
  • 78
  • 111
  • Of course you reuse cells, if use case allows that! Otherwise things can get really complicated. Anyway, the very first row added to tableView WILL be "the very first row". Sorry, I see no problem with that? – JOM Aug 23 '11 at 07:01
  • Clarification for my lonely "reuse cells" comment. It was a response to another comment, which now has been removed by someone. I got also a down vote for that! No worries, I'll survive :) – JOM Aug 25 '11 at 07:14
  • I just grabbed the contents of this action to handle figuring out which button in which UITableView cell called for a Segue. This technique is great there too! – jerwood Mar 05 '12 at 00:08
  • `tindexPathForCell:` will return the proper index path but everytime it gives a different reference. – geekay Apr 30 '12 at 05:59
  • 3
    "[sender superview] superview]" no longer works for me in iOS 7. – Daniel Ryan Oct 18 '13 at 00:42
  • 1
    Should loop over the superviews until UITableViewCell class is found – d2burke Jul 30 '14 at 23:04
  • Now in iOS 7 it should be `UITableViewCell *cell = (UITableViewCell *)[[[sender superview] superview] superview]` – Victor Nov 06 '14 at 09:40
  • Edward's [answer](http://stackoverflow.com/a/12049671/1145804) should be used instead so as to not depend on view hierarchy. – rebello95 Jun 22 '16 at 17:27
10

You could use the tag property on the button to specify which row the button was created in, if you're not using tags for anything else.

pix0r
  • 31,139
  • 18
  • 86
  • 102
5

For a implementation that is not dependent on tags or the view hierarchy do the following

- (void)btnPressed:(id)sender event:(id)event
{
  UITouch *touch = [[event allTouches] anyObject];
  CGPoint touchPoint = [touch locationInView:self.tableView];
  NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:touchPoint];
}
Edward Huynh
  • 2,907
  • 1
  • 27
  • 26
4

I have the same scenario. To achieve this, I derived a custom cell. I added two properties, section and row. I also added an owner, which would be my derived TableViewController class. When the cells are being asked for, I set the section/row based on the indexPath, along with the owner.

cell.section = indexPath.section cell.row = indexPath.row cell.owner = self

The next thing that I did was when I created the buttons, I associate the button events with the cell rather than with the tableViewController. The event handler can read the section and row entry and send the appropriate message (or event) to the TableViewController. This greatly simplifies house keeping and maintenance by leveraging existing methods and housekeeping and keeping the cell as self contained as possible. Since the system keeps track of cells already, why do it twice!

3

Even easier:

-(IBAction) buttonPressed {
    NSIndexPath *myIndexPath = [(UITableView *)self.superview indexPathForCell: self];
    // do whatever you need to do with the information
}
William Jockusch
  • 26,513
  • 49
  • 182
  • 323
3

Here's a Swift example...

The UIControl class is the superclass of various iOS widgets, including UIButton, because UIControl provides the target/action mechanism that sends out the event notifications. Therefore a generic way to handle this is as follows:

func actionHandler(control: UIControl)
   var indexPath = tableView.indexPathForCell(control.superview!.superview! as UITableViewCell)!
   var row = indexPath.row
}

Here's an example of setting up a button control to deliver the action. Alternatively, create an @IBAction and create the action visually with Interface Builder.

button.addTarget(self, action: "actionHandler:", forControlEvents: .TouchUpInside)

You can downcast UIControl parameter to UIButton, UIStepper, etc... as necessary. For example:

var button = control as UIButton

The superview of control is the UITableViewCell's contentView, whose subviews are the UIViews displayed in the cell (UIControl is a subclass of UIView). The superview of the content cell is the UITableViewCell itself. That's why this is a reliable mechanism and the superviews can be traversed with impunity.

clearlight
  • 12,255
  • 11
  • 57
  • 75
1

You can access the buttons superview to get the UITableViewCell that contains your button, but if you just need the row number, you can use the tag property like the previous post deacribes.

Marco Mustapic
  • 3,879
  • 1
  • 21
  • 20
  • Using [sender superview]; in the custom cell class (that contains the action methods for the buttons) gives me a UIView, not the cell. If I set the tag in the cellForRowAtIndexPath method, it still just comes as 0 for each button in the cell when pressed. – Tai Squared Jun 23 '09 at 16:29
  • That's right, you need to use [[sender superview] superview] as described by another post… this is then the cell, containing a UIView, which contains your button. – h4xxr Nov 16 '10 at 22:53
  • Here is a good solution: http://www.71squared.com/2008/12/custom-button-in-uitableviewcell-indexpath/ – DixieFlatline May 19 '11 at 10:02
1

There are multiple methods to fix the problem.

  1. You can use the "tag" property Give the value indexPath.row as the tag value for the button.
    btn.tag = indexPath.row;

Then in the button function, you can easily access the tag value and it will be the index for the clicked button.

-(void)btnClicked:(id)sender
{
    int index = [sender tag];
}
  1. You can use the layer property Add the indexPath as the value in the layer dictionary.

    [[btn layer] setValue:indexPath forKey:@"indexPath"];

This indexPath is accessible from the button action function.

-(void)btnClicked:(id)sender
{
    NSIndexPath *indexPath = [[sender layer] valueForKey:@"indexPath"];
    int index = indexPath.row;
}

With this method you can pass multiple values to the button function just by adding new objects in the dictionary with different keys.

Tinku George
  • 585
  • 5
  • 14
0
    [btnFavroite setAccessibilityValue:[NSString stringWithFormat:@"%d",indexPath.row]];
    [btnFavroite setAccessibilityLabel:btnFavroite.titleLabel.text];
    [btnFavroite addTarget:self action:@selector(btnFavClick:) forControlEvents:UIControlEventTouchUpInside];

-(void)btnFavClick:(id)sender{
    UIButton *btn=(UIButton *)sender;
    int index=[btn.accessibilityValue integerValue]]
}
Rinju Jain
  • 1,694
  • 1
  • 14
  • 23
0

this solution also works in IBAction connected using storyboard cell prototype

- (IBAction)viewMapPostsMarker:(UIButton*)sender{
    // button > cellContentView > cellScrollView > cell
    UITableViewCell *cell = (UITableViewCell *) sender.superview.superview.superview; 
    NSIndexPath *index = [self.mapPostsView indexPathForCell:cell];
    NSLog(@" cell at index %d",index.row);
}
Fareed Alnamrouti
  • 30,771
  • 4
  • 85
  • 76