9

I've been looking around to find a solution to this, but can't seem to find one that works for me. I have a custom cell with a button inside. My problem is how do I pass the indexPath to the action method?

Right now I'm doing

 [cell.showRewards addTarget:self action:@selector(myAction:) forControlEvents:UIControlEventTouchUpInside];

In my cellForRowAtIndexPath method and my method is:

-(IBAction)myAction:(id)sender{
NSIndexPath *indexPath = [self.tableView indexPathForCell:(MyCustomCell *)[sender superview]];
NSLog(@"Selected row is: %d",indexPath.row);
}

Any tips? Thanks.

Baby Groot
  • 4,637
  • 39
  • 52
  • 71
John S
  • 1,687
  • 6
  • 21
  • 28

12 Answers12

8

Just want to add what I believe is the best solution of all: a category on UIView.

It's as simple as this:

- (void)somethingHappened:(id)sender
{
    NSIndexPath *indexPath = [self.tableView indexPathForCell:[sender parentCell]];

    // Your code here...
}

Just use this category on UIView:

@interface UIView (ParentCell)

- (UITableViewCell *)parentCell;

@end

@implementation UIView (ParentCell)

- (UITableViewCell *)parentCell
{
    UIView *superview = self.superview;
    while( superview != nil ) {
        if( [superview isKindOfClass:[UITableViewCell class]] )
            return (UITableViewCell *)superview;

        superview = superview.superview;
    }

    return nil;
}

@end
Dany Joumaa
  • 2,030
  • 6
  • 30
  • 45
8
cell.showRewards.tag = indexPath.row;

-(IBAction)myAction:(id)sender
{
UIButton *btn = (UIButton *)sender;
int indexrow = btn.tag;
NSLog(@"Selected row is: %d",indexrow);
}
Baby Groot
  • 4,637
  • 39
  • 52
  • 71
5

While I feel setting tag for the button is one way to go. You might need to write code to make sure each time the cell gets reused, the appropriate tag gets updated on the button object.

Instead I have a feeling this could work. Try this -

-(IBAction)myAction:(id)sender
{
CGPoint location            = [sender locationInView:self.tableView];
NSIndexPath *indexPath      = [self.tableView indexPathForRowAtPoint:location];
UITableViewCell *swipeCell  = [self.tableView cellForRowAtIndexPath:indexPath];

NSLog(@"Selected row: %d", indexPath.row);
//......
}

Essentially what you are doing is getting the coordinates of where the click happened with respect to your tableView. After getting the coordinates, tableView can give you the indexPath by using the method indexPathForRowAtPoint:. You are good to go after this...

Voila, you have not just the indexPath but also the actual cell where the click happened. To get the actual data from your datasource (assuming its NSArray), you can do -

[datasource objectAtIndex:[indexPath row]];

Erik van der Neut
  • 2,245
  • 2
  • 22
  • 21
Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
  • I was about to suggest NSIndexPath * indexPath = [tableView indexPathForRowAtPoint: [[[event touchesForView:button] anyObject] locationInView: tableView]]; but this is much cleaner, thanks! – phi Sep 13 '11 at 09:00
  • 3
    Hmm, am I missing something here? Is actually [sender locationInView:self.tableView]; working for you? – phi Sep 15 '11 at 11:33
  • Not for me. I get [UIButton locationInView:]: unrecognized selector sent to instanc. – djskinner Feb 18 '13 at 21:55
  • Another [UIButton locationInView:]: unrecognized selector – JulianB Mar 22 '13 at 23:03
  • For those getting an unrecognized selector crash with `locationInView`, that's because you can't call that on UIButton. You need to add `NSSet *touches = [event touchesForView:sender]` and `UITouch *touch = [touches anyObject]`, then call `locationInView` on the `touch` variable. – bmueller Dec 04 '15 at 19:25
3

Try this one.

cell.showRewards.tag=indextPath.row

implement this in cellforrowatindexpath tableview's method.

-(IBAction)myAction:(id)sender{
UIButton* btn=(UIButton*)sender;
NSLog(@"Selected row is: %d",btn.tag);
}
Paras Gandhi
  • 823
  • 1
  • 13
  • 28
3

You set the button tag value = indexpath and check it in function if tag value is this do what u want

Rahul Juyal
  • 2,124
  • 1
  • 16
  • 33
3

In custom UITableViewCell class:

[self.contentView addSubview:but_you]; 

In cellForRowAtIndexPath method you can write:

[cell.showRewards addTarget:self action:@selector(myAction:) forControlEvents:UIControlEventTouchUpInside];    
cell.showRewards.tag = indexPath.row;
damirstuhec
  • 6,069
  • 1
  • 22
  • 39
Narayana Rao Routhu
  • 6,303
  • 27
  • 42
  • adding target part was the much-needed part, just found the issue I was trying the selector with # as if it were swift, but thanks to you suddenly I realized my mistake. Thanks. – Rakshitha Muranga Rodrigo Aug 01 '21 at 20:13
2

You can assign indexpath to button tag and access in your method like

cell.showRewards.tag = indexPath.row;

-(IBAction)myAction:(id)sender
{
     NSIndexPath *indexPath = [self.tableView indexPathForCell:[sender tag]];
     NSLog(@"Selected row is: %d",indexPath.row);
}
Janak Nirmal
  • 22,706
  • 18
  • 63
  • 99
1

I find it incredible that there isn't really a decent solution to this.

For whatever reason, I find the tagging methods and the 'using the visual location of the cell on the screen to identify the correct model object' outlined in the other answers a bit dirty.

Here are two different approaches to the problem:

Subclassing UITableViewCell

The solution I went with was to sub class UITableViewCell

@interface MyCustomCell : UITableViewCell

@property (nonatomic, strong) Model *myModelObject;

@end

When creating the cell in cellForRowAtIndexPath: you are likely to be using the model object to populate the cell data. In this method you can assign the model object to the cell.

And then in the button tap handler:

MatchTile *cell = (MatchTile *) sender.superview.superview;

if (cell && cell.myModelObject)
{
    //Use cell.myModelObject
}

I'm not 100% happy with this solution to be honest. Attaching domain object to such a specialised UIKit component feels like bad practice.

Use Objective-C Associative Objects

If you don't want to subclass the cell there is a another bit of trickery you can use to associate the model object with the cell and retrieve it later.

To retrieve the model object from the cell, you will need a unique key to identify it. Define one like this:

static char* OBJECT_KEY = "uniqueRetrievalKey"; 

Add the following line to your cellForRowAtIndexPath: method when you are using the model object to populate the cell. This will associate your model object with the cell object.

objc_setAssociatedObject(cell, OBJECT_KEY, myModelObject, OBJC_ASSOCIATION_RETAIN);

And then anywhere you have a reference to that cell you can retrieve the model object using:

MyModelObject *myModelObject = (MyModelObject *) objc_getAssociatedObject(cell, OBJECT_KEY);

In reflection, although I opted for the first (because I'd already subclassed the cell), the second solution is probably a bit cleaner since it remains the responsibility of the ViewController to attach and retrieve the model object. The UITableViewCell doesn't need to know anything about it.

djskinner
  • 8,035
  • 4
  • 49
  • 72
1

In - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method write the code below:

  [cell.zoomButton addTarget:self action:@selector(navigateAction:) forControlEvents:UIControlEventTouchUpInside];
cell.zoomButton.tag=indexPath.row;

Then write a method like this:

-(IBAction)navigateAction:(id)sender
{
UIButton *btn = (UIButton *)sender;
int indexrow = btn.tag;
NSLog(@"Selected row is: %d",indexrow);

currentBook = [[bookListParser bookListArray] objectAtIndex:indexrow];

KitapDetayViewController *kitapDetayViewController;
if(IS_IPHONE_5)
{
    kitapDetayViewController = [[KitapDetayViewController alloc] initWithNibName:@"KitapDetayViewController" bundle:Nil];
}

else
{
    kitapDetayViewController = [[KitapDetayViewController alloc] initWithNibName:@"KitapDetayViewController_iPhone4" bundle:Nil];
}
kitapDetayViewController.detailImageUrl = currentBook.detailImageUrl;
kitapDetayViewController.bookAuthor = currentBook.bookAuthor;
kitapDetayViewController.bookName = currentBook.bookName;
kitapDetayViewController.bookDescription = currentBook.bookDescription;
kitapDetayViewController.bookNarrator=currentBook.bookNarrator;
kitapDetayViewController.bookOrderHistory=currentBook.bookOrderDate;
int byte=[currentBook.bookSizeAtByte intValue];
int mb=byte/(1024*1024);
NSString *mbString = [NSString stringWithFormat:@"%d", mb];
kitapDetayViewController.bookSize=mbString;
kitapDetayViewController.bookOrderPrice=currentBook.priceAsText;
kitapDetayViewController.bookDuration=currentBook.bookDuration;
kitapDetayViewController.chapterNameListArray=self.chapterNameListArray;
// [[bookListParser bookListArray ]release];
[self.navigationController pushViewController:kitapDetayViewController animated:YES];
}
Baby Groot
  • 4,637
  • 39
  • 52
  • 71
Mahboob Nur
  • 739
  • 2
  • 9
  • 34
1

If you want the indexPath of the button Detecting which UIButton was pressed in a UITableView describe how to.

basically the button action becomes:

 - (void)checkButtonTapped:(id)sender
    {
        CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
        NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
        if (indexPath != nil)
        {
         ...
        }
    }
Community
  • 1
  • 1
Randel S
  • 362
  • 4
  • 9
1

In [sender superview] you access not MyCustomCell, but it's contentView. Read UITableViewCell Class Reference:

contentView Returns the content view of the cell object. (read-only)

@property(nonatomic, readonly, retain) UIView *contentView

Discussion: The content view of a UITableViewCell object is the default superview for content displayed by the cell. If you want to customize cells by simply adding additional views, you should add them to the content view so they will be positioned appropriately as the cell transitions into and out of editing mode.

Easiest way to modify your code is to use [[sender superview] superview]. But this will stop working if you later modify your cell and insert button in another view. contentView appeared in iPhoneOS 2.0. Similar future modification will influence your code. That the reason why I don't suggest to use this way.

Nazik
  • 8,696
  • 27
  • 77
  • 123
Valeriy Van
  • 1,851
  • 16
  • 19
0

Here is the "Simplest Way" to do it (Tested on IOS11):

NSIndexPath *indexPath = [myTable indexPathForRowAtPoint:[[[sender superview] superview] center]];
Jouhar
  • 288
  • 4
  • 9