How can I pass data from a cell by pressing a button in the same cell. Lets say I have a table with 3 cells row, each cell has a label with different value. What I need is when I press the button in cell 2 pass that value to another ViewController. Passing the data between controller is not problem when I press on the cell it's self, but my problem is when I press the specific button and hold the value of some string from that cell.
Thanks
You have to delegate button action to controller. You should research it yourself but I'm gonna give you short example.
Let's assume you have a UITableViewCell , and of course UITableView has its own delegate methods like
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
these are selection of a cell, but you want to control a button action.
So you have a button inside cell like this
UIButton *someButton = [[UIButton alloc] initWithFrame:CGRectMake(0,0, 50, 50)];
[someButton addTarget:self action:#selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:someButton];
this is in your cell class.
and .h of your cell you write delegation like this.
#protocol SomeProtocol <NSObject>
- (void) buttonPressedInCell:(NSString *) data;
#end
and your delegate property in .h
#property (nonatomic, strong) id<SomeProtocol> delegate;
and your cell.m
finally you can give delegate to your button action
- (void) buttonClicked {
[delegate buttonPressedInCell];
}
so after this you can delegate this object whenver you want, in your case you want to give it to your UITableView let's do it.
you have like a CustomTableViewCell
and your controller you must yourController<SomeProtocol>
and you have to delegate your cell like yourCell.delegate = self;
then implement your delegate method
- (void) buttonPressedInCell:(NSString *) data {
// do something like pushing new controller
//your data is in your controller now
}
Hope this helps you.
More information about delegation here
Related
In my iPhone application, I am using a UITableView with custom table cells by subclassing UITableViewCell. Let's assume my custom cell class name as ItemTableViewCell. The relevant ItemTableViewCell.xib has a subview of UIView with the name of infoView.
I want that infoView identify single tap/touch it gets. If it is just a tap, I can do it by adding UITapGestureRecognizer using story board and having a action method for that. But what I need is while identify the tap/touch of infoView, I need to pass the relevant table row info to the target method/selector.
If I further explain the situation, UITableView consists of NSArray of UITableViewCell. That custom cell has a bottom bar with a subview called infoView. By tapping/touching that infoView it should call a method with the related NSArray element as parameter. By tap/touching the rest of the cell, it should call didSelectRowAtIndexPath method as normal.
I have found a solution. While I'm creating custom cells, I'm adding UITapGestureRecognizer to the relevant UIVIew (in my case it's infoView) like below.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tappedOnInfoView:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[cell.infoView addGestureRecognizer:tap];
[cell.infoView setUserInteractionEnabled:YES];
[cell.infoView setTag:realIndex];
As you can see from the last line, I'm passing the cell index rather than the cell data to the selector which will fire after identify tap gesture.
At the same time I'm maintaining an NSMutableArray to store data of the cell. As you can understand, there will be simultaneous NSArray elements for each cell index.
This is how my selector appears;
- (void)tappedOnInfoView:(UIGestureRecognizer *)sender {
By using below code you can get the index related to the tap guesture occured
sender.view.tag
Using follow code snippet I'm able to get the details of cell where I've store in a NSMutableArray
[myMutArray objectAtIndex:sender.view.tag];
Mission accomplished :)
Implement gesture action in Tableview SuperClass. Or You can use Custom delegate in UITableViewCell subclass. In UITableView Subclass declare a protocol.
#protocol customCellDelegate <NSObject>
#required
-(void)selectedCellInIndexPath : (NSIndexPath *)indexpath;
#end
Set this property in UITableView Subclass
#property (nonatomic, strong)NSIndexPath *indexpath;
#property (nonatomic, strong) id <customCellDelegate> delegate;
And then in Your UITableView Subclass gesture action add This lines
if(self.delegate){
[self.delegate selectedCellInIndexPath: self.indexpath];
}
And in your tableview datasource method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
Implement this code
cell.delegate = (id)self;
cell.indexpath = indexPath;
And in Uitableview super class just implement this method
-(void)selectedCellInIndexPath : (NSIndexPath *)indexpath{
//display uiimagepickercontroller modally or anything else here
}
I have a custom uitableviewcell and subclassed, and it is containing a uitextfield and delegate is also set, now when return key on keyboard is pressed I want to try few things
perform a segue(but issue is I am in uitableviewcell subclass).
modally present another view controller(but issue is uitableviewcell
do not allow this).
I want to display uiactionsheet(but again limitation is
uitableviewcell).
If i get rootviewcontroller reference then rootviewcontroller's view itself not displayed or not the active view so any thing you do will not present on screen, active view is required.
You could use a block property on your cell that is fired whenever your custom button action occurs. Your cell's block property might look something like this:
#interface CustomTableViewCell : UITableViewCell
#property (nonatomic, copy) void (^customActionBlock)();
#end
Your cell would then invoke this block from the custom button action like this:
#implementation CustomTableViewCell
- (IBAction)buttonTapped:(id)sender {
if ( self.customActionBlock ) {
self.customActionBlock();
}
}
#end
Then finally, you set the block in -cellForRowAtIndexPath: back in your view controller (or wherever) like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"customCell" forIndexPath:indexPath];
cell.textLabel.text = [self.colors objectAtIndex:indexPath.row];
cell.customActionBlock = ^{
NSLog(#"Do the stuff!");
// present view controller modally
// present an action sheet
// etc....
};
return cell;
}
One word of caution, though. If you use blocks you run the risk of strongly referencing self and creating a memory leak. Blocks are fun and easy to use but you have to play by their rules. Here are some resources to help you get familiar with them:
Retain cycle on `self` with blocks
Reference to self inside block
http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html
http://fuckingblocksyntax.com
You can attach action to your buttons even if they are in a tableView
[cell.button addTarget:self action:#selector(presentController:) forControlEvents:UIControlEventTouchUpInside];
presentController is referring to an IBAction
- (IBAction)presentController:(id)sender
{
//present
}
Implement button action in Tableview SuperClass.
Or You can use Custom delegate in UITableViewCell subclass. In UITableView Subclass declare a protocol.
#protocol customCellDelegate <NSObject>
#required
-(void)selectedButtonInIndexPath : (NSIndexPath *)indexpath;
#end
Set this property in UITableView Subclass
#property (nonatomic, strong)NSIndexPath *indexpath;
#property (nonatomic, strong) id <customCellDelegate> delegate;
And then in Your UITableView Subclass Button action add This lines
if(self.delegate){
[self.delegate selectedButtonInIndexPath: self.indexpath];
}
And in your tableview datasource method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
Implement this code
cell.delegate = (id)self;
cell.indexpath = indexPath;
And in Uitableview super class just implement this method
-(void)selectedButtonInIndexPath : (NSIndexPath *)indexpath{
//display uiimagepickercontroller modally or anything else here
}
I have a custom cell with a label that should trigger a method of parent/table view controller.
Even though XCode provides me that method in autocomplete suggestions, it throws an error when I tap on that particular label:
UITableViewWrapperView showUserProfile unrecognized selector sent to instance
This is my code:
#implementation ItemTableViewCell
#synthesize item;
- (void)awakeFromNib
{
self.authorLabel.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showUserProfile)];
[self.authorLabel addGestureRecognizer:tap];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
}
- (void)showUserProfile
{
id parentView = [self superview];
[parentView showUserProfile];
}
#end
Thanks!
it is not the good way to passing some actions to the tableview. You must use a delegate between the custom class UITableViewCell and your controller.
In your custom UITableViewCell.h
#protocol myUITableViewCellDelegate
#interface myUITableViewCell : UITableViewCell
#property (nonatomic, assign) id<myUITableViewCellDelegate> delegate;
#end
#protocol myUITableViewCellDelegate <NSObject>
-(void) cellDidTap:(myUITableViewCell*) sender
#end
In your custom UITableViewCell.m
...
- (void)showUserProfile
{
[self.delegate cellDidTap:self];
}
...
In your controller
-(UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexpath
{
......
cell.delegate = self
......
}
-(void) cellDidTap:(myUITableViewCell*) sender
{
[self showUserProfile];
}
There is a difference between a view and a view controller. A view controller's class is UIViewController (or a subclass) and it has a property view of class UIView which it controls and which is the "visible part" of the view controller.
In the method
- (void)showUserProfile
{
id parentView = [self superview];
[parentView showUserProfile];
}
you call showUserProfile on the table view cell's superview but not on the view controller. As you don't know the internal implementation of a UITableView you cannot even be sure that the cell's superview is the same as your table view controller's view. In fact, it is not. Because as you can see from the error log the table view itself has a subview of class UITableViewWrapperView which contains all the cells. But this view doesn't know anything about the method you declared in your table view controller. That is why the app crashes.
For calling a method in your table view controller you can either declare a delegate as suggested by tdelepine or you can add an action to your button in your table view controller's tableView:cellForRowAtIndexPath: method right after dequeuing the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"yourID"];
[cell.yourButton addTarget:self action:#selector(showUserProfile) forControlEvents:UIControlEventTouchUpInside];
// customize other properties of your cell
return cell;
}
This would be the easiest way to go in my opinion.
You can add the controller (your ViewController that hosts UITableView for example) to your customized cell. Then, you use something like:
if ([self.controller respondsToSelector:#selector("afunctioname")]) {
[self.controller performSelector:#selector("afunctionname")];
}
Your customizedCell: MyTableViewCell header
#property (nonatomic, assign) id controller;
In the method cellforRow;
MyTableViewCell *aCell = ...
aCell.controller = self
..
return aCell;
You can add parameters to this selector if the 'afunctioname' takes parameters. If you have multi parameters, use NSDictionary as parameter.
I don't know if this is considered to be an accepted Objective-C practice or not, so I'm open to other ideas. Here is the idea. I have a table that gets is cells from a custom UITableViewCell. Each of these cells presents an event that the user can attend. As such, I'd like for the user to be able to add them to their calendar directly from the table view.
To accomplish this, I put a button on each table cell - an "add to calendar button". What I am stuck on is how to wire an action from this button back to the UIViewController where the UITableView is a subview. The button is part of a UITableView class and doesn't have visibility to the UIViewController.
I've been trying to implement the delegate pattern suggested by Aaron below. I'm almost there, but something is still disconnected. Here is what I have:
New protocol EventDelegate.h
#protocol EventDelegate <NSObject>
- (void) addToCalendar : (NSString *) strDate;
#end
In MyTableCell.h, I have added this property:
#property (nonatomic, strong) id<EventDelegate> eventDelegate;
In MyTableCell.m, I have added this method:
- (IBAction)addToCalendar:(UIButton *)sender
{
NSLog(#"calling addToCalendar delegate %#", _dayAndTime.text);
[self.eventDelegate addToCalendar:_dayAndTime.text];
}
All is well to this point. When I click on the button that I added to the table cell, I get the output calling addToCalendar delegate Monday, January 13
Over in MyViewController.h, I changed it look like this:
#interface TrainingScheduleViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, EventDelegate>
- (void) addToCalendar:(NSString *)strDate;
#end
And finally, in MyViewController.m I added the method body:
- (void) addToCalendar:(NSString *)strDate
{
NSLog(#"inside delegate");
NSLog(#"%#", strDate);
}
The part that I think might be the problem is where Aaron suggests adding this line of code:
[tableViewController setEventDelegate:self];
First, I'm not sure where to add this line. I put it in viewDidLoad. The compiler wouldn't let me type it verbatim, so the closest thing that I could find was this line:
[self.tableView setDelegate:self];
Maybe I need an additional outlet?
I have to be almost there, but I just don't see what I am still missing. Can anyone help me? Thanks!
Here is some example code that I pulled from my "cellForRowAtIndexPath" method. This is from a table that was built in IB and uses a prototype cell, but the idea is the same no matter how you implement. In this case, I use a tag to identify the button, then reference it when the cell is created:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
UIButtonRed *actionButton = (UIButtonRed *)[cell.contentView viewWithTag:4];
[actionButton addTarget:self action:#selector(initializeReorder:) forControlEvents:UIControlEventTouchDown];
return cell;
}
The initializeReorder: method automatically receives (id)sender as a parameter. You can cast that to a table cell and inspect it to get the rest of your info:
- (void)initializeReorder:(id)sender
{
UIButtonRed *actionButton = (UIButtonRed*)sender;
UITableViewCell *cell = (UITableViewCell*)actionButton.superview.superview;
NSIndexPath* cellPath = [self.tableView indexPathForCell:cell];
...
}
Create a protocol for this.
I would define a protocol like this:
#protocol MyEventDelegate <NSObject>
- (void)addToCalendar:(Event*)event;
#end
And add a delegate property to both your UITableViewController class and your MyTableCell class:
#property (nonatomic, strong) id<MyEventDelegate> eventDelegate;
MyViewController should conform to this protocol and implement addToCalendar:
#interface MyViewController : UIViewController <MyEventDelegate>
When your MyViewController object sets up the UITableViewController, pass in a reference to itself:
[tableViewController setEventDelegate:self];
and when your UITableViewController creates each cell, pass it on:
[cell setEventDelegate:self.eventDelegate];
Now, when the IBAction is called in your cell, the cell can call the delegate method on MyViewController like so:
[self.eventDelegate addToCalendar:event];
I am currently creating a custom grid view, which means that I am creating a class that has a lot in common with UITableView. One of the things that I want to get right is the communication of the cells and the grid view.
I was therefore wondering how a table view cell talks to its table view. For example, how does the cell notify the table view that its delete button was tapped and the cell needs to be removed from the table view?
There are several possible scenarios, but I am not sure which one is being used by Apple since the headers of UITableView or UITableViewCell reveal this (or am I overlooking something).
Ultimately, the goal is to let the cell and the grid view communicate in private, that is, without exposing any public methods or protocols (if this is possible).
Now a delete button might be a poor example because iOS has a built in method which allows you to delete rows and notify your datasource called:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
However, for the sake of understanding if you wanted to add a button to your tableview cell and have it perform an action that isn't in the standard iOS library you would create a delegate in your cell and set your tableview's datasource file as the delegate.
Basically you would subclass UITableViewCell like so
MyCustomCell.h
#protocol MyCustomCellDelegate;
#interface MyCustomCell : UITableViewCell
#property (nonatomic, unsafe_unretained) id <MyCustomCellDelegate> delegate; //Holds a reference to our tableView class so we can call to it.
#property (nonatomic, retain) NSIndexPath *indexPath; //Holds the indexPath of the cell so we know what cell had their delete button pressed
#end
/* Every class that has <MyCustomCellDelegate> in their .h must have these methods in them */
#protocol MyCustomCellDelegate <NSObject>
- (void)didTapDeleteButton:(MyCustomCell *)cell;
#end
MyCustomCell.m
#synthesize delegate = _delegate;
#synthesize indexPath = _indexPath;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
/* Create a button and make it call to a method in THIS class called deleteButtonTapped */
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(5, 5, 25, 25);
[button addTarget:self action:#selector(deleteButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
/**
* This is the method that is called when the button is clicked.
* All it does is call to the delegate. (Whatever class we assigned to the 'delegate' property)
*/
- (void)deleteButtonTapped:(id)sender
{
[self.delegate didTapDeleteButton:self];
}
Your TableView's datasource would look something like this.
MyDataSource.h
/* We conform to the delegate. Which basically means "Hey you know those methods that we defined in that #protocol I've got them and you can safely call to them" */
#interface MyDataSource : UIViewController <MyCustomCellDelegate, UITableViewDelegate, UITableViewDataSource>
#property (nonatomic,retain) NSArray *tableData;//We will pretend this is the table data
#property (nonatomic,retain) UITableView *tableView;// We will pretend this is the tableview
#end
MyDataSource.m
//We will pretend we synthesized and initialized the properties
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier: #"MyCustomCell"];
if (!cell)
cell = [[DownloadQueueCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: #"MyCustomCell"];
cell.delegate = self; // Make sure we set the cell delegate property to this file so that it calls to this file when the button is pressed.
cell.indexPath = indexPath;// Set the indexPath for later use so we know what row had it's button pressed.
return cell;
}
- (void)didTapDeleteButton:(MyCustomCell *)cell;
{
// From here we would likely call to the apple API to Delete a row cleanly and animated
// However, since this example is ignoring the fact that they exist
// We will remove the object from the tableData array and reload the data
[self.tableData removeObjectAtIndexPath:cell.indexPath];
[self.tableView reloadData];
}
Basically, long story short. For your gridview you would just create a delegate method that tells the user a certain button was pressed.
UITableViewCell items are subviews of UITableView. So you could use it to communicate between cells and tableView. In its turn UITableView has the delegate and datasource to communicate with its controller. This might help.
I'm not sure that a private communication channel is needed.
The table view imposes a delete view adjacent to a given cell by resizing the table view cell and creating a new view in the open space.
The imposed delete view is instantiated with the table view, the index path, and the table view delegate. The delete view handles the touch and sends a message to the table view delegate including the table view and index path. The table view delegate does the work of removing the entry from the data source, animating the cell removal and refreshing the table view. Upon refresh, the table view redraws all the visible cells according to the data source.
You can have your custom cell UIViews have a private property of the type of your Grid View. When you add these cells to your GridView, update that property to the gridView.
I have my custom grid and do it this way.
Another way is having a method in your grid to pass a cell, and that will return you the index. UITableView has those methods too. That way when a button in a cell is pressed, all you have to do is get the cell and pass it to the grid, that will return an index. With that index you access the data...
You may use categories.
You declare your private methods in a separate category, and place it to the separate file. In the implementation file of class which wants to use these private methods, you import this file with private category, and use the private methods. So the public .h of the class which uses them is left intact.
Example:
MyGridViewCell.h:
#interface MyGridViewCell : UIView
// ...
#end
MyGridViewCell.m:
#implementation MyGridViewCell : UIView
// ...
#end
Now the private methods category interface:
MyGridViewCellPrivate.h:
#interface MyGridViewCell (Private)
- (void) privateMethod1;
#end
And implementation:
MyGridViewCellPrivate.m:
#implementation MyGridViewCell (Private)
- (void) privateMethod1
{
// ...
}
#end
Header remains the same as before:
MyGridView.h:
#interface MyGridView : UIView
- (void) publicMethod1;
#end
But the implementation may use the private API:
MyGridView.m:
#import "MyGridViewCell.h"
#import "MyGridViewCellPrivate.h"
- (void) publicMethod1
{
// Use privateMethod1
}