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
Create and Store PDF Document Programmatically Using Swift for iOS
Auto Layout Constraint on Calayer iOS
@Import "Unexpected '@' in Program"
"'Cdvplugin.H' File Not Found" in Cordova as Component (Cleaver)
Border Around Uitableview Section
Service Workers and iOS/Safari
How to Select Range of Values When Using Arc4Random()
How to Get Visible Viewcontroller from App Delegate When Using Storyboard
Uicollectionview Registerclass: Forcellwithreuseidentifier Method Breaks Uicollectionview
Facebook Sdk 3.0: How to Receive User's E-Mail
Drop Cap with Nsattributedstring
How to Trap the Back Button Event
Can't Install Watchkit App on Apple Watch
Watch Os 2.0 Beta: Access Heart Beat Rate
How to Access Swift 4 Class from Objective-C: "Property Not Found on Object of Type"