How to Find Indexpath for Tapped Button in Tableview Using Seque

How to find indexPath for tapped button in tableView Using Seque

Forget for a moment that you are using storyboard, and let's try to follow the best approach.

You have several solution, but for my opinion, just 1 of these is optimal: delegation pattern.

First, you should extend your cell, and using delegation to return the cell when the user press the button. Then, you should use indexPathForCell to get the indexPath.

Let's see the approaches:

Cell by button position

- (void)buttonClicked:(id)sender
CGPoint buttonPosition = [sender convertPoint:CGPointZero
toView:self.tableView];
NSIndexPath *tappedIP = [self.tableView indexPathForRowAtPoint:buttonPosition];

// When necessary
// UITableViewCell *clickedCell = [self.tableView cellForRowAtIndexPath:tappedIP];
}

the solution above is certainly the most rapid to implement, but it is not the best from the point of view of the design/architecture. Moreover you get the indexPath but you need then to calculate every other info. This is a cool method, but would say not the best.

Cell by while cycle on the button superviews

// ContactListViewController.m

- (IBAction)emailContact:(id)sender {
YMContact *contact = [self contactFromContactButton:sender];
// present composer with `contact`...
}

- (YMContact *)contactFromContactButton:(UIView *)contactButton {
UIView *aSuperview = [contactButton superview];
while (![aSuperview isKindOfClass:[UITableViewCell class]]) {
aSuperview = [aSuperview superview];
}
YMContactCell *cell = (id) aSuperview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
return [[self fetchedResultsController] objectAtIndexPath:indexPath];
}

Get the cell in this way is less performant of the former, and it is not elegant as well.

Cell by button tag

- (CustomCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
cell.theLabel.text = self.theData[indexPath.row];
cell.button.tag = indexPath.row;
[cell.button addTarget:self action:@selector(doSomething:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}

-(void)doSomething:(UIButton *) sender {
NSLog(@"%@",self.theData[sender.tag]);
//sender.tag will be equal to indexPath.row
}

Absolutely no. Use the tag could seem a cool solution, but the tag of a control can be used for other reasons, like the next responder etc. I don't like this approach.

Cell by design pattern

// YMContactCell.h
@protocol YMContactCellDelegate
- (void)contactCellEmailWasTapped:(YMContactCell*)cell;
@end

@interface YMContactCell
@property (weak, nonatomic) id<YMContactCellDelegate> delegate;
@end

// YMContactCell.m
- (IBAction)emailContact:(id)sender {
[self.delegate contactCellEmailWasTapped:self];
}

// ContactListViewController.m
- (void)contactCellEmailWasTapped:(YMContactCell*)cell;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
YMContact *contact = [[self fetchedResultsController] objectAtIndexPath:indexPath];
// present composer with `contact` ...
}

This is my favourite solution.
Using delegation or blocks is a really nice approach and you can pass all the parameters you want. In fact you could want to send back directly the needed info, without having to calculate them later.

Enjoy ;)

How to find indexPath for tapped accessory button in tableView

As you have discovered, the system sends you the prepareForSegue:sender: message before it sends you the tableview:accessoryButtonTappedForRowWithIndexPath: message.

But, when it sends you the prepareForSegue:sender: message, the sender argument is the UITableViewCell containing the accessory view. You can use that to determine which row's accessory button was tapped:

else if ([segue.identifier isEqualToString:@"detailsSegue"]) {
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
Annotation *annotation = [self.viewsList objectAtIndex:indexPath.row];
DetailsViewController *dvc = segue.destinationViewController;
dvc.wikiKey = annotation.title;
}

How to get the indexPath of a UIButton in a UITableViewCell?

Thanks to all i slove this by using below code

     NSIndexPath *indexPath = [panelTableView indexPathForCell:(UITableViewCell *)sender.superview.superview];

NSLog(@"%d",indexPath.row);

How to get indexPath over touched button?

It's not working because you do not select row when you click button.

With two buttons I would disconnect your segues from buttons and do 2 manual segues in IB then add code like that to handle them:

-(void)button1Pressed:(id)sender {
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *clickedButtonIndexPath = [self.homeTableView indexPathForCell:clickedCell];
...
[self performSegueWithIdentifier:@"YourManualSegueIdentifier1" sender:self];
}

Edit for second option:

In your MyTableViewCell.h:

@interface MyTableViewCell : UITableViewCell
...
@property (strong, nonatomic) NSIndexPath *cellIndexPath;
...
@end

In your MyTableViewCell.m:

-(void)button1Pressed:(id)sender {
...
NSInteger row = cellIndexPath.row;
...
}

In your MyTableViewController.m:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// remember index here!
[cell setCellIndexPath:indexPath];
....
return cell;
}

How to get the indexPath.row when I click a button on a table view cell?

A common, generalized solution for this type of problem is to connect the @IBAction of the button to a handler in the cell (not in the view controller), and then use a delegate-protocol pattern so the cell can tell the table when the button was tapped. The key is that when the cell does this, it will supply a reference to itself, which the view controller can then use to determine the appropriate indexPath (and thus the row).

For example:

  • Give your UITableViewCell subclass a protocol:

    protocol CustomCellDelegate: class {
    func cell(_ cell: CustomCell, didTap button: UIButton)
    }
  • Hook up the @IBAction to the cell (not the view controller) and have that call the delegate method:

    class CustomCell: UITableViewCell {
    weak var delegate: CustomCellDelegate?

    @IBOutlet weak var customLabel: UILabel!

    func configure(text: String, delegate: CustomCellDelegate) {
    customLabel.text = text
    self.delegate = delegate
    }

    @IBAction func didTapButton(_ button: UIButton) {
    delegate?.cell(self, didTap: button)
    }
    }
  • Obviously, when the cell is created, call the configure method, passing, amongst other things, a reference to itself as the delegate:

    extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
    let text = ...
    cell.configure(text: text, delegate: self)
    return cell
    }
    }
  • Finally, have the delegate method call indexPath(for:) to determine the index path for the cell in question:

    extension ViewController: CustomCellDelegate {
    func cell(_ cell: CustomCell, didTap button: UIButton) {
    guard let indexPath = tableView.indexPath(for: cell) else { return }

    // use `indexPath.row` here
    }
    }

The other approach is to use closures, but again using the same general pattern of hooking the button @IBAction to the cell, but have it call a closure instead of the delegate method:

  • Define custom cell with closure that will be called when the button is tapped:

    class CustomCell: UITableViewCell {
    typealias ButtonHandler = (CustomCell) -> Void

    var buttonHandler: ButtonHandler?

    @IBOutlet weak var customLabel: UILabel!

    func configure(text: String, buttonHandler: @escaping ButtonHandler) {
    customLabel.text = text
    self.buttonHandler = buttonHandler
    }

    @IBAction func didTapButton(_ button: UIButton) {
    buttonHandler?(self)
    }
    }
  • When the table view data source creates the cell, supply a handler closure:

    extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
    let text = ...
    cell.configure(text: text, buttonHandler: { [weak self] cell in // the `[weak self]` is only needed if this closure references `self` somewhere
    guard let indexPath = tableView.indexPath(for: cell) else { return }

    // use `indexPath` here
    })
    return cell
    }
    }

I personally prefer the delegate-protocol pattern, as it tends to scale more nicely, but both approaches work.


Note, in both examples, I studiously avoided saving the indexPath in the cell, itself (or worse, “tag” values). By doing this, it protects you from getting misaligned if rows are later inserted and deleted from the table.


By the way, I used fairly generic method/closure names. In a real app, you might give them more meaningful names, e.g., didTapInfoButton, didTapSaveButton, etc.) that clarifies the functional intent.

How to get indexPath over touched button?

It's not working because you do not select row when you click button.

With two buttons I would disconnect your segues from buttons and do 2 manual segues in IB then add code like that to handle them:

-(void)button1Pressed:(id)sender {
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *clickedButtonIndexPath = [self.homeTableView indexPathForCell:clickedCell];
...
[self performSegueWithIdentifier:@"YourManualSegueIdentifier1" sender:self];
}

Edit for second option:

In your MyTableViewCell.h:

@interface MyTableViewCell : UITableViewCell
...
@property (strong, nonatomic) NSIndexPath *cellIndexPath;
...
@end

In your MyTableViewCell.m:

-(void)button1Pressed:(id)sender {
...
NSInteger row = cellIndexPath.row;
...
}

In your MyTableViewController.m:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// remember index here!
[cell setCellIndexPath:indexPath];
....
return cell;
}

How to get indexPath when image inside cell tapped

1: set tag of ImageView in cellForRowAtIndexpath method , tag will be equal to indexPath.row

imgView.tag = indexPath.row

2: add a target to tapGuestureRecognizer attached on imageView

3: get tag of imageView in that method

let imgView = sender as! UIImageView
let tag = imgView.tag

4: get data accordingly ,and push

let next = self.storyBoard.instatiateViewController(WithIdentifer:"detailVC") 
next.data = datas[tag]
self.navigationController.pushViewController(next)


Related Topics



Leave a reply



Submit