I created a tableViewCell the include an image, two text labels and a uibutton.
The button is allocated to an action method (e.g. viewButtonPused:sender).
I'm used to handle row selection with tableView:didSelectRowAtIndexPath: so I could tell which row was selected. But with the uibutton and its action method .... How can I tell?
Thanks in advance.
If the button's target is the UIViewController/UITableViewController or any other object that maintains a reference to the UITableView instance, this will do nicely:
- (void)viewButtonPushed:(id)sender {
UIButton *button = (UIButton *)sender;
UITableViewCell *cell = button.superview; // adjust according to your UITableViewCell-subclass' view hierarchy
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// use your NSIndexPath here
}
Using this approach will let you avoid extra instance variables and will work fine in case you have multiple sections. You need to have a way to access the UITableView instance though.
Edit: as someone pointed out in the comments below, this approach broke in iOS 7. If you're still interested in using this approach over tags, be sure to find the UITableViewCell instance correctly, i.e. by looping through the superviews until you find one.
Define a delegate on the class associated with the Cell's prototype.
// MyCell.h
#protocol MyCellDelegate
- (void)buttonTappedOnCell:(MyCell *)cell;
#end
#interface MyCell : UITableViewCell
#property (nonatomic, weak) id <MyCellDelegate> delegate;
#end
// MyCell.m
#implementation MyCell
- (void)buttonTapped:(id)sender {
[self.delegate buttonTappedOnCell:self];
}
}
#end
Now go to the class you want to make the Cell's delegate. This is probably going to be a UITableView subclass. In the cellForRowAtIndexPath method make sure you assign the delegate of the Cell to self. Then implement the method specified in the protocol.
- (void)buttonTappedOnCell:(MyCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
int row = indexPath.row;
}
Or if you would prefer a blocks based approach:
// MyCell.h
typdef void(^CellButtonTappedBlock)(MyCell *cell);
#interface MyCell : UITableViewCell
#property (nonatomic, copy) CellButtonTappedBlock buttonTappedBlock;
#end
Then in your tableView's dataSource:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = ....
__weak typeof(self) weakSelf = self;
[cell setButtonTappedBlock:^(MyCell *cell) {
NSIndexPath *indexPath = [weakSelf.tableView indexPathForCell:cell];
// Do stuff with the indexPath
}];
}
I know this is an old thread but I find this method best as it is free from superView calls (and thus when Apple change the view hierarchy with new os versions it is left unaffected) and doesn't require subclassing or use of cumbersome tags that can get messed up when the cell is reused.
- (void)buttonPressed:(UIButton *)sender {
CGPoint location = [sender convertPoint:sender.center toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
NSLog(#"%#", indexPath);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
[cell.customCellButton addTarget:self action:#selector(customCellButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[cell.customCellButton setTag:indexPath.row];
}
- (void)customCellButtonTapped:(id)sender {
UIButton *button = (UIButton *)sender;
NSLog(#"indexPath.row: %d", button.tag);
}
If you have directly added elements on the cell itself (which you shouldnt) -
NSIndexPath *indexPath = [self.tableView indexPathForCell:(UITableViewCell *)[sender superview] ];
If you have added elements on the contentView of the cell (which is the proposed way)
NSIndexPath *indexPath = [self.tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview] ];
define one class variable int SelectedRow; inside didSelectRowAtIndexPath assign value to it like
SelectedRow = indexPath.row;
use this SelectedRow variable in side your action method viewButtonPused:sender
Instead of adding the button as a subview of the cell set it to be cell.accessoryView.
Then use tableView:accessoryButtonTappedForRowWithIndexPath: to do your stuff that should be done when a user taps this button.
Another way that I use now is to subclass UIButton and add a property of type NSIndexPath.
On cellForRowAtIndexPath I assign the value of indexPath and its available at the action method.
Related
This question already has answers here:
How to know the UITableview row number
(10 answers)
Closed 9 years ago.
I have a UITableView for which I have created a custom UITableViewCell. Each row in tableview has a button. I want to know the section number on click of a button, so that I would know that from which section button has been clicked. I have already tried few things found on stack but nothing is working.
UIButton *b = sender;
NSIndexPath *path = [NSIndexPath indexPathForRow:b.tag inSection:0];
NSLog(#"Row %d - Section : %d", path.row, path.section);
Don't know what you've tried, but i might do something like this. Doing some pseudocode from memory, here.
- (void)buttonClicked:(id)sender {
CGPoint buttonOrigin = [sender frame].origin;
// this converts the coordinate system of the origin from the button's superview to the table view's coordinate system.
CGPoint originInTableView = [self.tableView convertPoint:buttonOrigin fromView:[sender superview];
// gets the row corresponding to the converted point
NSIndexPath rowIndexPath = [self.tableView indexPathForRowAtPoint:originInTableView];
NSInteger section = [rowIndexPath section];
}
If I'm thinking clearly, this gives you flexibility in case the button's not directly inside the UITableView cell. Say, if you've nested inside some intermediary view.
Sadly, there doesn't seem to be an iOS equivalent of NSTableView's rowForView:
Create a handler for button click and add it in tableView:cellForRowAtIndexPath: method
- (void)buttonPressed:(UIButton *)button{
UITableViewCell *cell = button.superView.superView;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
//Now you have indexPath of the cell
//do your stuff here
}
When you create your custom UITableViewCell in cellForRowAtIndexPath you should pass it its section as a parameter. It could look like:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
if (!cell)
{
cell = [[[MyCustomCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MyIdentifier" section:indexPath.section] autorelease];
}
return cell;
}
Now your cell knows its section and you can use it when performing click method in MyCustomCell class
Try this,
First assign section as a tag to button also add target on button in cellForRowAtIndexPath method.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
[cell.btnSample setTag:indexPath.section];
[cell.btnSample addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
...
}
Get Section as tag from sender of IBAction you defined (buttonClicked here).
-(IBAction)buttonClicked:(id)sender
{
NSLog(#"Section: %d",[sender tag]);
}
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.
cell.showRewards.tag = indexPath.row;
-(IBAction)myAction:(id)sender
{
UIButton *btn = (UIButton *)sender;
int indexrow = btn.tag;
NSLog(#"Selected row is: %d",indexrow);
}
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
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]];
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);
}
You set the button tag value = indexpath and check it in function if tag value is this do what u want
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;
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);
}
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.
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.
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];
}
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)
{
...
}
}
Here is the "Simplest Way" to do it (Tested on IOS11):
NSIndexPath *indexPath = [myTable indexPathForRowAtPoint:[[[sender superview] superview] center]];
I have a simple problem: I can't call "[tableView reloadData]" from a UIButton in an UITableViewCell .m.
I have a tableView that show the UITableViewCell that contain a UIButton on each row. When I click on the button of the cell, I want to reloadData from my tableView.
As long as you hold a reference to your tableView, you should be able to reload the data by hitting a button. The easiest way to do this is to make a reference in your header file
#interface MyClass ... {
UITableView *myTableView;
// all your other stuff;
}
// any methods and properties you want to declare;
#end
Then when you put your buttons into the cell in your - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method, do something like the following
UIButton *myButton = [UIButton buttonWithType:whateverTypeYouPick];
[myButton addTarget:self action:#selector(reloadTableView) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:myButton]; // or cell.contentView or wherever you want to place it
Then simply set up your action method
- (IBAction)reloadTableView {
[myTableView reloadData];
// anything else you would like to do;
}
I tested this out and it works fine for me, so hopefully it does the trick for you as well
One of the way would be to have delegate on the Cell and make the tableViewController implement the delegates when the action happens.
MyCell.h
#protocol MyCellDelegate
-(void)myCell:(MyCell*)cell reloadTableView:(id)sender;
#end
#interface MyCell : UITableViewCell
#property (nonatomic, weak) id <MyCellDelegate> delegate;
-(IBAction)reloadTableView:(id)sender;
#end
MyCell.m
#implementation MyCell
#property (nonatomic, weak) id <MyCellDelegate> delegate;
-(IBAction)reloadTableView:(id)sender;
{
if(self.delegate)
{
[self.delegate myCell:self reloadTableView:sender];
}
}
#end
Implement the delegate method in the tableViewController and do the task you want to perform.
-(void)myCell:(MyCell*)cell reloadTableView:(id)sender;
{
CGPoint location = [sender locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
//Here is the indexPath
[self.tableView reloadData];
}
I have a UITableViewCell (with associated UITableViewCell sub class, .m & .h) created in IB which contains a UITextField. This UITextField is connected up to an IBOutlet in the UITableViewCell sub class and also has a property. In my table view controller I am using this custom cell with the following code:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"textfieldTableCell"];
if (cell == nil) {
// Create a temporary UIViewController to instantiate the custom cell.
UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:#"TextfieldTableCell" bundle:nil];
// Grab a pointer to the custom cell.
cell = (TextfieldTableCell *)temporaryController.view;
// Release the temporary UIViewController.
[temporaryController release];
}
return cell;
}
The UITextField displays fine and the keyboard pops up when clicked as expected, but how do I access (get .text property) the UITextField that each row contains? and also how do I handle the 'textFieldShouldReturn' method of the UITextFields?
I think what the OP is trying to understand is how to access the UITextField value once the user has entered data into each fields. This will not be available at the time the cells are created as suggested by #willcodejavaforfood.
I've been implementing a form and trying to make it as user friendly as possible. It is doable but be aware that it can get quite convoluted depending on the number of UITableViewCells / UITextFields you have.
Firstly to your question re: accessing the values of UITextField:
1) Make your view controller a <UITextFieldDelegate>
2) Implement the following method:
- (void) textFieldDidEndEditing:(UITextField *)textField {
NSIndexPath *indexPath = [self.tableView indexPathForCell:(CustomCell*)[[textField superview] superview]]; // this should return you your current indexPath
// From here on you can (switch) your indexPath.section or indexPath.row
// as appropriate to get the textValue and assign it to a variable, for instance:
if (indexPath.section == kMandatorySection) {
if (indexPath.row == kEmailField) self.emailFieldValue = textField.text;
if (indexPath.row == kPasswordField) self.passwordFieldValue = textField.text;
if (indexPath.row == kPasswordConfirmField) self.passwordConfirmFieldValue = textField.text;
}
else if (indexPath.section == kOptionalSection) {
if (indexPath.row == kFirstNameField) self.firstNameFieldValue = textField.text;
if (indexPath.row == kLastNameField) self.lastNameFieldValue = textField.text;
if (indexPath.row == kPostcodeField) self.postcodeFieldValue = textField.text;
}
}
I also use a similar syntax to make sure the current edited field is visible:
- (void) textFieldDidBeginEditing:(UITextField *)textField {
CustomCell *cell = (CustomCell*) [[textField superview] superview];
[self.tableView scrollToRowAtIndexPath:[self.tableView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
And finally, you can handle textViewShouldReturn: in a similar way:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSIndexPath *indexPath = [self.tableView indexPathForCell:(CustomCell*)[[textField superview] superview]];
switch (indexPath.section) {
case kMandatorySection:
{
// I am testing to see if this is NOT the last field of my first section
// If not, find the next UITextField and make it firstResponder if the user
// presses ENTER on the keyboard
if (indexPath.row < kPasswordConfirmField) {
NSIndexPath *sibling = [NSIndexPath indexPathForRow:indexPath.row+1 inSection:indexPath.section];
CustomCell *cell = (CustomCell*)[self.tableView cellForRowAtIndexPath:sibling];
[cell.cellTextField becomeFirstResponder];
} else {
// In case this is my last section row, when the user presses ENTER,
// I move the focus to the first row in next section
NSIndexPath *sibling = [NSIndexPath indexPathForRow:kFirstNameField inSection:kOptionalSection];
MemberLoginCell *cell = (MemberLoginCell*)[self.memberTableView cellForRowAtIndexPath:sibling];
[cell.cellTextField becomeFirstResponder];
}
break;
}
...
}
In cellForRowAtIndexPath: include this code,
yourTextField.tag=indexPath.row+1; //(tag must be a non zero number)
Then access the textField using
UITextField *tf=(UITextField *)[yourView viewWithTag:tag];
There is even more simpler way to solve both problems,
1.Create a custom uitableviewCell class for the cell, (e.g.textfieldcell)
2.Now, in the textfieldcell.h file call textFieldDelegate
3.In the textfieldcell.m file write textFieldDelegate methods
ie
-(BOOL)textFieldShouldReturn:(UITextField *)textField;
-(void)textFieldDidEndEditing:(UITextField *)textField;
(first problem)Now, in
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self.mytextBox resignFirstResponder];
return YES;
}
5.(second problem),
-(void)textFieldDidEndEditing:(UITextField *)textField
{
nameTextField = mytextBox.text;
}
6.create a custom delegate method in the MaintableViewController
#protocol textFieldDelegate <NSObject>
-(void)textName:(NSString *)name;
#end
7.In MaintableViewController.m file write the implementation of the delegate method,
-(void)textName:(NSString *)name{
Nametext = name;
NSLog(#"name = %#",name);
}
8.call the delegate method in the cell class , and pass the variable in the didendmethod
9.now, assign self to cell.delegate ,when initializing the cell in uitableview
10.thats it you got the variable passed from textfield to the main view, Now do whatever u want with the variable
This is how I managed to get the text inside the UITextField inside my custom UITableViewCell in Swift. I accessed this inside my UIButton inside another custom UITableViewCell that has an #IBAction on my UITableViewController. I only have one section in my UITableViewController but that doesn't matter anyway because you can easily set and assign this yourself.
#IBAction func submitButtonTapped(sender: UIButton) {
print("Submit button tapped")
let usernameCell = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as! UsernameTableViewCell
print("Username: \(usernameCell.usernameTextField.text)")
}
Whenever I tapped my UIButton, it gives me the updated value of the text inside my UITextField.
If you have created a class for your custom cell I'd advise you to work against it.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell* cell = (MyCustomCell *) [tableView dequeueReusableCellWithIdentifier:#"BDCustomCell"];
if (cell == nil) {
// Load the top-level objects from the custom cell XIB.
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyCustomCell" owner:self options:nil];
// Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
cell = (MyCustomCell *) [topLevelObjects objectAtIndex:0];
}
// This is where you can access the properties of your custom class
cell.myCustomLabel.text = #"customText";
return cell;
}
This tutorial was helpful to me. You can reference whatever object you need through the tag.
In the Storyboard drag on a UIImageView or UITextField etc. and set the tag to 100 (whatever you want) then in your - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath use the tag to reference it.
Here's something you could do, just remember to set the tags in the storyboard:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UITextField *tField = (UITextField *)[cell viewWithTag:100];
return cell;
}
I have a table view where each cell has a button accessory view. The table is managed by a fetched results controller and is frequently reordered. I want to be able to press one of the buttons and obtain the index path of that button's table view cell. I've been trying to get this working for days by storing the row of the button in its tag, but when the table gets reordered, the row becomes incorrect and I keep failing at reordering the tags correctly. Any new ideas on how to keep track of the button's cell's index path?
If you feel uncomfortable relying on button.superview, this method should be a little more robust than some of the other answers here:
UIButton *button = (UIButton *)sender;
CGRect buttonFrame = [button convertRect:button.bounds toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonFrame.origin];
This stopped working with iOS 7; check out Mike Weller's answer instead
- (IBAction)clickedButton:(id)sender {
UIButton *button = (UIButton *)sender;
UITableViewCell *cell = (UITableViewCell *)button.superview;
UITableView *tableView = (UITableView *)cell.superview;
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
}
Or shorter:
- (IBAction)clickedButton:(id)sender {
NSIndexPath *indexPath = [(UITableView *)sender.superview.superview indexPathForCell:(UITableViewCell *)sender.superview];
}
Both are untested!
Crawling up view hierarchies with .superview (like all of the existing answers demonstrate) is a really bad way to do things. If UITableViewCell's structure changes (which has happened before) your app will break. Seeing .superview.superview in your code should set off alarm bells.
The button and its handler should be added to a custom UITableViewCell subclass and layed out there. That's where it belongs.
The cell subclass can then delegate out the button event through a standard delegate interface, or a block. You should aim for something like this:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCustomCell *cell = ...;
// ...
cell.onButtonTapped = ^{
[self buttonSelectedAtIndexPath:indexPath];
}
// OR
cell.delegate = self;
// ...
}
(Note: if you go the block route, you will need to use a __weak self reference to prevent retain cycles, but I thought that would clutter up the example).
If you take the delegate route you would then have this delegate method to implement:
- (void)cellButtonPressed:(UITableViewCell *)cell
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// ...
}
Your code now has full access to the appropriate context when it handles the event.
Implementing this interface on your cell class should be straightforward.
I don't know why I need to call the method superview twice to get the UITableViewCell.
Update:
Thank for Qiulang, now I got it.
"That's because SDK now has added a private class called UITableViewCellContentView for UITableViewCell, which is button's superview now." – Qiulang
UIButton *button = (UIButton *)sender;
UITableViewCell *cell = (UITableViewCell *)button.superview.superview;
UITableView *curTableView = (UITableView *)cell.superview;
NSIndexPath *indexPath = [curTableView indexPathForCell:cell];
I had this same issue also and built a simple recursive method that works no matter how many views deep you triggering control is.
-(NSIndexPath*)GetIndexPathFromSender:(id)sender{
if(!sender) { return nil; }
if([sender isKindOfClass:[UITableViewCell class]])
{
UITableViewCell *cell = sender;
return [self.tableView indexPathForCell:cell];
}
return [self GetIndexPathFromSender:((UIView*)[sender superview])];
}
-(void)ButtonClicked:(id)sender{
NSIndexPath *indexPath = [self GetIndexPathFromSender:sender];
}
I have created one Method for getting indexPath, Hope this will help you.
Create Button Action (aMethod:) in cellForRowAtIndexPath
-(void) aMethod:(UIButton *)sender
{
// Calling Magic Method which will return us indexPath.
NSIndexPath *indexPath = [self getButtonIndexPath:sender];
NSLog(#"IndexPath: %li", indexPath.row);
NSLog(#"IndexRow: %li", indexPath.section);
}
// Here is the Magic Method for getting button's indexPath
-(NSIndexPath *) getButtonIndexPath:(UIButton *) button
{
CGRect buttonFrame = [button convertRect:button.bounds toView:groupTable];
return [groupTable indexPathForRowAtPoint:buttonFrame.origin];
}
Use this Perfect working for me.
CGPoint center= [sender center];
CGPoint rootViewPoint = [[sender superview] convertPoint:center toView:_tableView1];
NSIndexPath *indexPath = [_tableView1 indexPathForRowAtPoint:rootViewPoint];
NSLog(#"%#",indexPath);
SWIFT 2 UPDATE
Here's how to find out which button was tapped
#IBAction func yourButton(sender: AnyObject) {
var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(position)
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath!)! as
UITableViewCell
print(indexPath?.row)
print("Tap tap tap tap")
}