i have looked up the same questions, but the answers given don't seem to fix my problem.
I'm creating an app where you can add and remove your school marks. In my view controller i have created a UITableView. Within there there's a custom UITableViewCell, called TableViewCell. To delete a cell i need to get it's row. I try that by using the following code in the TableViewCell class:
- (IBAction)DeleteMark:(id)sender {
UITableView *superTableView = [self superview];
NSIndexPath *path = [superTableView indexPathForCell:self];
NSInteger *index = [path row];
NSLog([NSString stringWithFormat:#"%#", index]);
}
When i run the code it shows the following error:
-[UITableViewWrapperView indexPathForCell:]: unrecognized selector sent to instance 0xa825a00
As i said before i tried the solutions given on the forums, but none of them worked.
EDIT
Here's my code for cellForRowAtIndexPath
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
float weight = [[_editableClass.markWeights objectAtIndex:indexPath.row]floatValue];
float mark = [[_editableClass.classMarks objectAtIndex:indexPath.row]floatValue];
cell.MarkTextLabel.text = [NSString stringWithFormat:#"%.1f", mark];
cell.MarkWeight.text = [NSString stringWithFormat:#"%.1f", weight];
[cell.DeleteButton setTag:indexPath.row];
return cell;
}
To do something like what you are after you could try to set up a delegate to pass back the cell object so you can find the index from the tableView. I have tried to make an example below. For tidiness I have omitted import statements.
ViewController header file
#interface subClassViewController : UIViewController<UITableViewDataSource, UITableViewDelegate,TableViewCellDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#end
ViewController implementation file
#implementation subClassViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
//setup cell...
cell.delegate = self;
return cell;
}
- (void)deletePressedOnCell:(UITableViewCell *)cell
{
NSInteger index = [self.tableView indexPathForCell:cell].row;
// do something with index
}
#end
TableViewCell header file
#protocol TableViewCellDelegate
- (void)deletePressedOnCell:(UITableViewCell *)cell;
#end
#interface TableViewCell : UITableViewCell
#property (weak, nonatomic) id<TableViewCellDelegate>delegate;
#end
TableViewCell implementation file
#implementation TableViewCell
- (IBAction)DeleteMark:(id)sender
{
[self.delegate deletePressedOnCell:self];
}
#end
In your UITableview Delegate method cellForRowAtIndexPath set tag of your button:-
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
.....
cell.yourdeletebutton.tag=indexPath.row;
}
Now in your delete method it looks like:-
- (IBAction)DeleteMark:(id)sender {
UIButton *tappedButton = (UIButton*)sender;
DeleteIndex=tappedButton.tag;
NSLog(#"delete row at indexpath :%#", DeleteIndex]);
}
You can use by [sender tag],
[cell.btnDelete setTag:indexPath.row];
[cell.btnDelete addTarget:self action:#selector(DeleteMark:) forControlEvents:UIControlEventTouchUpInside];
Method,
- (IBAction) DeleteMark:(id)sender {
NSInteger *index = [sender tag];
NSLog([NSString stringWithFormat:#"%#", index]);
// Your Code...
//For Eg.
Object *obj = [YOUR_ARRAY objectAtIndex:[sender tag]];
// You'll get object of particular index.
// Your Code...
}
Thanks.
You shouldn't rely on [[[[sender superview] superview] superview] superview], what if Apple decide to make some changes and add/remove another layer of subview.
The idea with tags is not ideal neither.
I would recommended you to use delegate pattern or blocks.
Please have a look how closures works. Note that it's written in swift but it could be easily translated to Objective-C.
In your TableViewCell add handler property:
var buttonPressHandler: (() -> ())?
Add IBAction which must be connected to button touch event (you could do it in storyboard):
#IBAction func buttonPressed(sender: AnyObject) {
// Call handler when button is pressed
buttonPressHandler?()
}
In cellForRowAtIndexPath you only needs to set up the handler:
cell.buttonPressHandler = {
print("Button pressed: \(indexPath.row)")
}
Get the index path of the row using following code
- (void)tapped:(UIButton *) sender {
UITableViewCell * tCell;
NSComparisonResult order = [[UIDevice currentDevice].systemVersion compare: #"7.2" options: NSNumericSearch];
if (order == NSOrderedDescending) {
tCell = (BusinessFavoriteCell *)[[[[sender superview] superview] superview] superview];
} else {
tCell = (BusinessFavoriteCell *)[[[sender superview] superview] superview];
}
NSIndexPath * iPath = [self.tableView indexPathForCell:tCell];
}
Here, tCell is the UITableViewCell which is loaded in cellForRowIndexpath method.
Related
I have a tableview with custom tableview cell. in the tableview cell there are two labels and one button.what I want it to fire the button action for user selected row to hide a label in the same row.
this is my controller for table view
ViweController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>
#property (weak, nonatomic) IBOutlet UITableView *tablev;
#end
ViewController.m
#import "ViewController.h"
#import "TestTableViewCell.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TestTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"mycell"];
cell.selectionStyle = UITableViewCellFocusStyleCustom;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger sec = indexPath.section;
NSInteger rw = indexPath.row;
TestTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"mycell"];
cell.numberlabel.hidden = YES;
NSLog(#"selected section :%li ---> selected row :%li",(long)sec, (long)rw);
//in here I want fire the button acction in the cell for each row when cell tap.(not when the button click in the cell).
}
TestTableViewCell.h
#import <UIKit/UIKit.h>
#interface TestTableViewCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *staticlabel;
#property (weak, nonatomic) IBOutlet UILabel *numberlabel;
#property (weak, nonatomic) IBOutlet UIButton *hidebutton;
#end
TestTableViewCell.m
//I have tried to implement button click method here.It worked.but at that point it didn't recognised which cell is taped.
**NOTE : I have tried to implement button click method here.I worked.but at that point it didn't recognised which cell is taped. **
You can implement by two way one you can add Button Action in to your cellForRowAtIndexPath and setting tag of Button like following code:
hidebutton.tag=indexPath.row;
[hidebutton addTarget:self
action:#selector(hideaction:)
forControlEvents:UIControlEventTouchUpInside];
Its Action Method is
-(IBAction)hideaction:(UIButton*)sender
{
NSIndexPath *hideIndexpath = [NSIndexPath indexPathForRow:sender.tag inSection:0];
TestTableViewCell *cell = (TestTableViewCell *)[self.tablev cellForRowAtIndexPath:hideIndexpath];
}
Another way is you can achieve this same from DidSelect method with following code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
TestTableViewCell *cell = (TestTableViewCell *)[self.tablev cellForRowAtIndexPath:indexPath];
//use your cell object for hide anyting
}
You can get indexPath.row of UITableView in button action:
Make action of button in yourviewcontroller.h file:
- (IBAction) My_button:(id)sender;
In yourviewcontroller.m file:
- (IBAction)My_button:(id)sender
{
CGPoint buttonPosition = [sender convertPoint:CGPointZero
toView:self.tbl_view];
NSIndexPath *indexPath = [self.tbl_view indexPathForRowAtPoint:buttonPosition];
NSLog(#"%ld",(long)indexPath.row);
}
And If you want to do this in your didSelectRowAtIndexPath then you don't need to dequeue cell again.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%ld",(long)indexPath.row);
NSLog(#"%ld",(long)indexPath.section);
TestTableViewCell *cell = (TestTableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
cell.numberlabel.hidden = YES;
}
you need to get the correct cell first , You can achieve it by replacing
TestTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"mycell"];
by this:
TestTableViewCell *cell = (TestTableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
Your method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger sec = indexPath.section;
NSInteger rw = indexPath.row;
TestTableViewCell *cell = (TestTableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
cell.numberlabel.hidden = YES;
NSLog(#"selected section :%li ---> selected row :%li",(long)sec, (long)rw);
//in here I want fire the button acction in the cell for each row when cell tap.(not when the button click in the cell).
}
Hope this Helps!
Normally, if I wanted to have a UITextField as a part of a UITableViewCell, I would likely use either a) static rows or b) I would create the cell in the storyboard, outlet the cell and outlet the field to my ViewController, and then drag the cell outside of the "Table View", but keeping it in the scene.
However, I need to create a View where I accept input from 28 various things. I don't want to outlet up 28 different UITextField's.
I want to do this dynamically, to make it easier. So I've created a custom UITableViewCell with a Label and UITextField.
My ViewController has two arrays.
#property (nonatomic, strong) NSArray *items;
#property (nonatomic, strong) NSArray *itemValues;
My cellForRowAtIndexPath looks something like this...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = #"ItemCell";
MyItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if (!cell) {
cell = [[MyItemTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
[cell.itemValue addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
} else {
if (![cell.itemValue actionsForTarget:self forControlEvent:UIControlEventEditingChanged]) {
[cell.itemValue addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
}
}
cell.item.text = [self.items objectAtIndex:indexPath.row];
cell.itemValue.text = [self.itemValues objectAtIndex:indexPath.row];
return cell;
}
- (void)textFieldDidChange:(id)sender
{
NSLog(#"textFieldDidChange: %zd", [self.tableView indexPathForSelectedRow].row);
}
This is proving to be problematic. textFieldDidChange always returns [self.tableView indexPathForSelectedRow].row as 0, as the cell of course, has never been selected. I'm stumped on how I could even find out which row's UITextField has been edited, so I can update the corresponding itemValues array.
UITableView has a neat method that converts a point in the tableView to an indexPath, indexPathForRowAtPoint:.
First you have to convert the origin of your textField to the frame of the UITableView.
- (void)textFieldDidChange:(UITextField *)sender
{
CGPoint textFieldOriginInTableView = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:textFieldOriginInTableView];
if (indexPath) {
NSLog(#"TextField at indexPath %# did change", indexPath);
}
else {
NSLog(#"Error: Can't calculate indexPath");
}
}
The easiest way to do this is to just tag the textfield with indexPath.row and get it back via [sender tag] in the delegate method.
I'm having a problem in getting a value in uitableviewcell.
first of all, i have a label on my cell which I've created a tap gesture on it. What i want to do is on tapping that label, viewUser method will be call and it will get the details of that cell being tapped.
Here is my code:
on cellForRowAtIndexPath:
cell.userNameLabel.text = [[_workflowList objectAtIndex:indexPath.row] userName];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(viewUser:)];
tapGestureRecognizer.numberOfTapsRequired = 1;
[cell.userNameLabel addGestureRecognizer:tapGestureRecognizer];
cell.userNameLabel.userInteractionEnabled = YES;
Now, when i call my ontap method which is viewUser:
- (IBAction)viewUser:(id)sender {
//this should not be hard coded
//data should come from cell.
//usernamelabel or i will get the index selected
//and get the details in my array
// like this --> [_workflowList objectAtIndex:index]
WorkflowProfileViewController *chkDtl = [[WorkflowProfileViewController alloc]init];
chkDtl.name = #"USER, USER USER"; ;
chkDtl.phoneNo = #"09173210836";
chkDtl.email = #"romelync#sxchange.com";
[self.navigationController pushViewController:chkDtl animated:YES];
}
Please help me on this.
The best option would be to use UITableView delegate method tableView:didSelectRowAtIndexpath: as below:
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
WorkflowProfileViewController *chkDtl = [[WorkflowProfileViewController alloc]init];
chkDtl.name=[[_workflowList objectAtIndex:indexPath.row] userName];
[self.navigationController pushViewController:chkDtl animated:YES];
}
Don't forget to set delegate of your tableView.
UPDATE: Then I think you want to do something like below. In your cellForRowAtaIndexPath: add following:
cell.userNameLabel.tag=indexPath.row;
And in viewUser method:
UITapGestureRecognizer *tap=(UITapGestureRecognizer*)sender;
WorkflowProfileViewController *chkDtl = [[WorkflowProfileViewController alloc]init];
chkDtl.name=[[_workflowList objectAtIndex:tap.view.tag] userName];
[self.navigationController pushViewController:chkDtl animated:YES];
When creating UILable you could put the tag value as row number
lable.tag = indexPath.row
In your method then retrieve label and look for tag value.
Something like below will get the cell you are tapping (replace swipe with tap):
- (void)handleSwipeRight:(UISwipeGestureRecognizer *)gestureRecognizer{
CGPoint location = [gestureRecognizer locationInView:self.tableView];
NSIndexPath *swipedIndexPath = [self.tableView indexPathForRowAtPoint:location];
self.currentSwipedCell = [self.tableView cellForRowAtIndexPath:swipedIndexPath];
}
Havent tested it yet but:
-(void)viewUser:(UITapGestureRecognizer):gestureRecognizer{
CGPoint location = [gestureRecognizer locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
self.currentTappedCell = [self.tableView cellForRowAtIndexPath:indexPath];
NSLog(self.currentTappedCell.name); // If you have a custom cell with a name property
}
Definitely you should use the proper delegate methods instead implementing UITapGestureRecognizer on an UITableViewCell. Then you can get the informations you want pretty easily:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *theUserNameYouWantToGet = [_workflowList objectAtIndex:[indexPath row]];
}
EDIT:
If you want that action not to work for the whole cell, you could subclass UITableViewCell, add properties to it and implement your own delegate protocol to that cell:
#protocol JWTableViewCellDelegate;
#interface JWTableViewCell : UITableViewCell
#property (nonatomic, retain) NSString *username;
#property (nonatomic, assign) id<JWTableViewCellDelegate>delegate;
#end
#protocol JWTableViewCell <NSObject>
- (void)tableViewCellDidClickUsername:(JWTableViewCell *)cell;
#end
#implementation JWTableViewCell
- (void)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self setupView];
}
return self;
}
// if you use a prototyping NIB
- (void)awakeFromNib {
[self setupView];
}
- (void)setupView {
// add gesture recognizer
}
- (void)handleGesture {
if ([_delegate respondsToSelector:#selector(tableViewCellDidClickUsername:)]) {
[_delegate tableViewCellDidClickUsername:self];
}
}
Then use this in your viewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
JWTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"reuseID" forIndexPath:indexpath];
[cell setUsername:#"Paul"];
[cell setDelegate:self];
}
- (void)tableViewCellDidClickUsername:(JWTableViewCell *)cell {
NSString *theUserNameYouWantToGet = [cell username];
}
I have a custom cell that have a check box,
all is working fine, the checkboxes get check according to a dictionary that I pass to my subclassed UITableViewCell,
but now I need to pass to the class that have the table view the exact cell that my check box was modified so I can set my mutable dictionary with the new checked or unchecked state for that particular cell,
So how to do this?, shall I use a delegate?, this is fine, but the question is, how do I know what cell was my check box modified at?
You can use a delegate like this...
MyCell.h
#protocol MyCellDelegate <NSObject>
-(void)cellCheckBoxWasChanged:(MyCell *)cell;
#end
#interface MyCell : NSObject
#property (nonatomic, weak) id <MyCellDelegate> delegate;
#end
MyCell.m
#implementation MyCell
- (void)checkBoxChanged
{
[self.delegate cellCheckBoxWasChanged:self];
}
#end
Then to find the index you can do...
TableViewController.m
- (void)cellCheckBoxWasChanged:(MyCell *)cell
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// do something to your array.
}
Why don't you pass the UITableViewCell also in the delegate method as self.
So with that cell, you could get the indexpath by
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
set the tag Value of each Checkboxes depending on the cells Indexpath in the cellForRowAtIndexPath: method.
UIButton *checkboxes = customCell.checkButton
[checkboxes setTag:indexPath.row];
then in buttons action method.
check the senders.Tag value to get the exact row of the button pressed
UITableViewCell *cell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:sender.tag inSection:0]];
you can do this -
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil)
{
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if([[[Usercontacts objectAtIndex:indexPath.row]objectForKey:#"isChecked"]isEqualToString:#"NO"])
{
[checkUncheckBtn setImage:[UIImage imageNamed:#"unchecked box.png" ]forState:UIControlStateNormal];
checkUncheckBtn.tag=1;
}
else
{
[checkUncheckBtn setImage:[UIImage imageNamed:#"checked box.png" ]forState:UIControlStateNormal];
checkUncheckBtn.tag=2;
}
[checkUncheckBtn addTarget:self action:#selector(checkUncheckBtnPressed:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
and when you perform checkUncheckBtnPressed: method it looks like
-(void)checkUncheckBtnPressed:(id)sender
{
UIButton *btn=(UIButton*)sender;
UITableViewCell *cell =(UITableViewCell *) [sender superview] ;
NSIndexPath *_indxpath = [createGroupContactsTableView indexPathForCell:cell];
if(btn.tag==1)
{
[btn setImage:[UIImage imageNamed:#"checked box.png" ]forState:UIControlStateNormal];
btn.tag=2;
[[Usercontacts objectAtIndex:_indxpath.row]setObject:#"YES" forKey:#"isChecked"];
}
else
{
[btn setImage:[UIImage imageNamed:#"unchecked box.png" ]forState:UIControlStateNormal];
btn.tag=1;
[[Usercontacts objectAtIndex:_indxpath.row]setObject:#"NO" forKey:#"isChecked"];
}
}
Here's an alternative to having the Cells listen to events from the checkBoxes and forward them to the UITableViewController using the delegate pattern:
Have the UITableViewController listen to events from the checkBoxes and use the following code to determine the NSIndexPath of the cell:
#implementation UITableView (MyCategory)
-(NSIndexPath*)indexPathOfCellComponent:(UIView*)component {
if([component isDescendantOfView:self] && component != self) {
CGPoint point = [component.superview convertPoint:component.center toView:self];
return [self indexPathForRowAtPoint:point];
}
else {
return nil;
}
}
#end
I have a "contact list" table view with "contact" cells that contain an email button that, when tapped, should present an email composer with the email address of that contact.
What is the best way to associate the UIButton with the "contact" instance of that cell?
I’ve created answers for the two approaches that come to mind – but which I don’t really find satisfactory. Which do you prefer, or much better still, suggest better ones!
Approach 2:
Make the cells handle the action and call a custom delegate method.
// 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` ...
}
Doesn’t handling events in a view violate the MVC principle?
The way I most often see it done is by assigning tags to the buttons that are equal to the indexPath.row.
- (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
}
Another solution:
For me something like this works flawlessly, and looks very elegant:
- (void)buttonClicked:(id)sender
CGPoint buttonPosition = [sender convertPoint:CGPointZero
toView:self.tableView];
NSIndexPath *clickedIP = [self.tableView indexPathForRowAtPoint:buttonPosition];
// When necessary
// UITableViewCell *clickedCell = [self.tableView cellForRowAtIndexPath:clickedIP];
}
Update 12/01/2017
After some time, and implementing lots of UITableViews, I need to admit that the best solution is using the delegation pattern, already suggested by others here.
Reading these answers, i would like say my opinion:
Cell by button position
- (void)buttonClicked:(id)sender
CGPoint buttonPosition = [sender convertPoint:CGPointZero
toView:self.tableView];
NSIndexPath *clickedIP = [self.tableView indexPathForRowAtPoint:buttonPosition];
// When necessary
// UITableViewCell *clickedCell = [self.tableView cellForRowAtIndexPath:clickedIP];
}
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 obtain the indexPath but need to calculate any 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 more expensive of the previous 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 can seems a cool solution, but the tag of a control can be used for a lot of things, like the next responder etc. I don't like and this is not the right way.
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 the solution. Use delegation or use blocks is really a nice thing to do because you can pass all parameters that you want and make the architecture scalable. In fact in the delegate method (but also with blocks) you could want send directly informations without having the need to calculate them later, like the previous solutions.
Enjoy ;)
Swift Closure Approach
I guess I found a new approach which is a bit swifty. Tell me what you think about it.
Your Cell:
class ButtonCell: UITableViewCell {
var buttonAction: ( () -> Void)?
func buttonPressed() {
self.buttonAction?()
}
}
Your UITableViewDataSource:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CallCell", for: indexPath)
//Handled inside
cell.buttonAction = {
//Button pressed
}
//Handle in method
cell.buttonAction = self.handleInnerCellButtonPress()
}
You can also pass data inside this call. Like the cell or something stored inside the cell.
Regards,
Alex
Approach 1:
Determine the cell, and thence the index path, by traversing the cell’s view hierarchy from the button.
// 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];
}
It feels clunky to me. Kinda "meh"…
I would provide another approach, just doesn't assign the active target, the event will traverse upon the responder chain:
[self.actionButton addTarget:nil action:#selector(onActionButtonClick:) forControlEvents:UIControlEventTouchUpInside];
and then, in your view controller:
- (void)onActorButtonClick:(id)sender {
if ([sender isKindOfClass:UIButton.class]) {
UITableViewCell *cell = [self findAncestorTableCell:(UIView *)sender]; //See other answer to fetch the cell instance.
NSIndexPath *indexPath = [self.listTable indexPathForCell:cell];
...
}
}
However, this begets some compiler warning, add this to ignore them:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
...
#pragma clang diagnostic pop