Call function from UIButton in UICellView - ios

I have UITableView with custom cells. On cell I have a buttons, and a method in my viewController (which contains UITableView)
My button click realisation is inside my myCustomCell class.
And the question is - What is the simplest way to call viewController method from myCustomCell?
I thought about delegates and NSNotificationCenter. But maybe there is another way.
EDIT

Put following lines in your myCustomCell.h
#protocol ButtonTapDelegate
- (void) buttonDidTap:(UIButton*) button;
#end
#property (nonatomic, weak) NSObject<ButtonTapDelegate> *vs_delegate;
-(void) buttonIsPressed;
in your myCustomCell.m
#synthesize vs_delegate;
-(void) buttonIsPressed:(UIButton*)button {
if([delegate conformsToProtocol:#protocol(ButtonTapDelegate)] && [delegate respondsToSelector:#selector(buttonDidTap:)]) {
[vs_delegate buttonDidTap:button];
}
}
In your viewController.h
myViewController : UIViewController <ButtonTapDelegate>
In your viewController.m, inside Method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[cell set.Vs_delegate:self];
[cell.button setTag:indexPath.row];
[cell.button addTarget:self action:#selector(buttonIsPressed) forControlEvents:UIControlEventTouchUpInside];
[cell.button buttonIsPressed:indexPath.row];
Put following method inside ViewController.m
- (void) buttonDidTap:(UIButton*) button {
// You have index, by Button's tag.
}

Most efficient and clean way to do this using blocks.
Declare a block property in your cell either TableViewCell or CollectionViewCell.
#property (nonatomic, copy) void(^buttonClickedAtIndexPath)(NSIndexPath *indexPath);
Declare button's action in Cell itself and Call above block in button click event.
-(IBAction)buttonClicked:(id)sender {
// get indexPath here, which will be indexPath of cell.
// you need to set button's tag as indexPath.row
if(self.buttonClickedAtIndexPath) {
self.buttonClickedAtIndexPath (indexPath);
}
}
In your ViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = // configure cell here.
cell.button.tag = indexPath.row;
cell.buttonClickedAtIndexPath = ^(NSIndexPath *indexPath){
// you can do anything here.
// Call any method using self OR
// perform segue
}
}
If you've CollectionView inside TableViewCell then same things applies.
Make a class MyCollectionViewCell subclassing UICollectionViewCell.
Declare block property in MyCollectionViewCell.
Handle all the events in MyCollectionViewCell (including display data, delegate, datasource for collectionView).
Call a block from MyCollectionViewCell on button click.
Declare a property of MyCollectionViewCell in your TableViewCell.
In your controller's cellForRowAtIndexPath do something like this.
============================================================
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = // configure cell here.
cell.mycollectionView.buttonClickedAtIndexPath = ^(NSIndexPath *indexPath){
// you can do anything here.
// Call any method using self OR
// perform segue
}
}

Related

Embed UITableView in UITableViewCell in iOS 9, Xcode 7 and Storyboards

I have two UITableViews using Storyboards in Xcode 7. I've set the delegate and dataSource using the Connections Inspector for both table views.
Let the first table view be the main table view and let the table views within each cell of the main table view be the detail table views with cell identifiers named appropriately and respectively.
When [tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath] executes, it immediately calls its dataSource method -cellForRowAtIndexPath: for DetailCell preventing me from setting a custom instance variable in time to add the appropriate data to each cell.
The following is a simplified example marked using comments.
MainTableViewController:
#implementation MainTableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Keep in mind the following two (2) lines are set using the Connections Inspector
//cell.detailTableView.dataSource = cell;
//cell.detailTableView.delegate = cell;
// Stepping over the following line will jump to the
// other `-cellForRowAtIndexPath:` (below) used to set
// the detail info.
cell = (MainTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
CustomObj *obj = self.mainData[indexPath.row];
cell.nameLabel.text = obj.name;
cell.additionalInfo = obj.additionalInfo; // This line is not set before instantiation begins for the detail table view...
return cell;
}
...
#end
DetailTableViewCell (contains a UITableView and implements appropriate protocols):
#interface DetailTableViewCell : UITableViewCell <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, weak) IBOutlet UILabel *nameLabel;
#property (nonatomic, weak) IBOutlet UITableView *detailTableView;
#property (nonatomic, strong) CustomObj *additionalInfo;
#end
#implementation DetailTableViewCell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
cell = (DetailTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"DetailCell" forIndexPath:indexPath];
// Instantiate detail ...
cell.detailLabel.text = self.additionalInfo.text;
// Problem!
// self.additionalInfo == nil thus we cannot set a value to the label.
return cell;
}
...
#end
The problem is when the detail -cellForRowAtIndexPath: method is called, I haven't had a chance to set a value for its dataSource, in this case, additionalInfo.
There are many possible ways to fix your problem, but first I would say that, your design seems not a good one, A UItableViewCell has another UITableView, and another UItableViewCell inside this UITableView? Why you do this? Just use one UITableView and put all of your views into one UItableViewCell as subViews should be enough.
Now get to your problem:
I would suggest not to use IBOutlet for setting up your delegate and dataSource, use code. This can give you a chance to delay setting the dataSource and delgate when you are ready. Once you think it's the proper time, just call [cell.detailTableView reloadData] will trigger your DetailTableViewCell to invoke cellForRowAtIndexPath
#implementation MainTableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Keep in mind the following two (2) lines are set using the Connections Inspector
//cell.detailTableView.dataSource = cell;
//cell.detailTableView.delegate = cell;
// Stepping over the following line will jump to the
// other `-cellForRowAtIndexPath:` (below) used to set
// the detail info.
cell = (MainTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
CustomObj *obj = self.mainData[indexPath.row];
cell.nameLabel.text = obj.name;
cell.additionalInfo = obj.additionalInfo; // This line is not set before instantiation begins for the detail table view...
// setup dataSource and delegate now
cell.detailTableView.dataSource = cell;
cell.detailTableView.delegate = cell;
// call reloadData whenever you think is proper
[cell.detailTableView reloadData];
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = nil;
//Check this call is for which table view.
if(tableView == detailTableView) {
cell = (MainTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
// Do any additional setup you want with MainCell
} else {
cell = (DetailTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"DetailCell" forIndexPath:indexPath];
// Do any additional setup you want with DetailCell
}
return cell;
}

How to overcome UITableViewCell subclass limitation

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
}

iOS - Dilemna - how to segue from customized cells

I have a segue from a view controller - with a enclosed tableview - to another view controller. I can segue from each cell in the first controller to the second with no problem. However, when I return to the first controller, the cell view is blank.
The dilemma is -- If I use this method:
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
the cell is visible but segues do not work.
Does anyone know of an alternative method?
Thanks
You need to create custom table view cell(may be you already have one, then tweak it), lets call it MyTableViewCell. Then add UITapGestureRecognizer to handle tap events on cell's contentView. When tap occurs you can execute custom block, which you should setup for cell. In block you can perform desired segue. But enough word, lets see some code!
First, lets define MyTableViewCell
MyTableViewCell.h
typedef void (^MyTableViewCellTapBlock) ();
#interface MyTableViewCell : UITableViewCell
#property (nonatomic, strong) MyTableViewCellTapBlock tapBlock;
#end
MyTableViewCell.m
#interface MyTableViewCell ()
#property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer;
#end
#implementation MyTableViewCell
- (void)awakeFromNib {
self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self.contentView addGestureRecognizer:self.tapRecognizer];
}
- (void)handleTap:(UITapGestureRecognizer *)recognizer {
NSLog(#"Tap logged");
if (self.tapBlock) {
self.tapBlock();
}
}
#end
Second, update your UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
__weak typeof(self) weakSelf = self;
cell.tapBlock = ^ {
[weakSelf performSegueWithIdentifier:#"showDetail" sender:weakSelf];
};
return cell;
}
Remarks
As you can see we have custom block that will be executed when user taps the cell. This block invokes performSegueWithIdentifier:, just do not forget to name your segue and change name in the sample.
Happy coding :)
Thanks for the efforts Keenie.. I will hold on to that code snippet and I know I'll use it.
This is embarrassing, but it turns out that all I needed to do, was on the return from the segue, to do [[self tableView] reloadData], and all was ok..

How to UIButton state in UITableViewCell?

Is there anyway to control UIButton state (enable/disable button) in UITableViewCell. My problem is my UIButton in cell is made in storyboard using viewWithTag. I've been spending quite a lot of time to sort it out but no luck. People mostly sort out the problem by programmatically assigning the tag for the button with cell indexPath.
I'm aware of that the table will reuse the cell, but I just want to ask if there is another hacky way to sort out my issue. If it is impossible, I might have to create the button programmatically.
You could loop through all the subviews of the cell and check if they are a UIButton using isMemberOfClass to get your button. If you have multiple buttons you could then check the text of the button or some other property that uniquely identifies it. That would be a hacky way to do it.
You have to make a custom cell like that:
CustomCell.h
#protocol CustomCellDelegate <NSObject>
- (void)buttonPressed:(UIButton *)sender;
#end
#import <UIKit/UIKit.h>
#interface CustomCell : UITableViewCell
#property (weak, nonatomic) id<CustomCellDelegate> delegate;
#property (weak, nonatomic) IBOutlet UIButton *button;
- (IBAction)buttonPressed:(UIButton *)sender;
#end
CustomCell.m
#import "CustomCell.h"
#implementation CustomCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)prepareForReuse{
self.button.enable = YES;
}
- (IBAction)buttonPressed:(UIButton *)sender{
[self.delegate buttonPressed:sender];
}
#end
after in IB you add a new UITableViewCell at your UITableView and the class of it with you new custom cell set the Identify ID like "CustomCell" add your Button to your custom cell and connect the Outlet, then you modify you tableView:cellForRowAtIndexPath: like that:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier=#"CustomCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.delegate = self;
return cell;
}
- (void)buttonPressed:(UIButton *)sender{
sender.enable = NO;
}
Also you have to add the CustomCellDelegate in your controller's heater file
One simple way would be to keep a NSMutableArray variable in your viewcontroller and with that keep track of what cells button is disabled/enabled. And use the UITableViewDataDelegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
to set the buttons state each time it gets displayed. And UITableViewDelegate method:
– tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)tableViewCell forRowAtIndexPath:(NSIndexPath *)indexPath
to write to the array. Indexing with indexPath.

Open Camera from within UITableView Cell

I've got a button in a custom Cell for a TableView which is supposed to open the camera for taking pictures.
I thought of two ways but can't get them working.
First one is to open an instance of UIImagePickerController from within the cell. Well, it seems like I can't call
[self presentViewController...];
from within the cell. Is this right?
Because of this "result" I thought of putting the method which opens up the UIImagePickerController inside the TableViewController and then call this method from within the cell (where the button is located) by something like
[super openCamera];
Or making the TableViewController the delegate of the cell to enable it to call the method.
Are these ideas going in the right direction? What would you recommend? Thank you very much!
Ok, I figured something out but I'm still wondering if it can be done easier.
Here is the solution I found:
In the custom cell I added
#property (nonatomic, assign) id adminController;
Then in the tableViewController I customized the following method to use the custom cell I created and set the tableViewController als "admin"
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
CreateCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.adminController = self;
return cell;
}
So I could finally call
[self.adminController performSelector:#selector(openCamera)];
This is an old question but I'd like to have my old questions answered too so... Yes, there is an easier way using blocks:
First, declare a public method in your UITableViewCell interface:
#interface YourCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UIButton *button;
- (void)setDidTapButtonBlock:(void (^)(id sender))didTapButtonBlock;
#end
In the UITableViewCell subclass implementation file declare a private property with a copy attribute.
#import "YourCell.h"
#interface YourCell ()
#property (copy, nonatomic) void (^buttonTappedBlock)(id sender);
#end
Add the target and action of the UIControl in the UITableViewCell constructor and implement the selector method
- (void)awakeFromNib {
[super awakeFromNib];
[self.button addTarget:self
action:#selector(didTapButton:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)didTapButton:(id)sender {
if (buttonTappedBlock) {
buttonTappedBlock(sender);
}
}
Finally implement the block code in the tableView:cellForRowAtIndexPath: method in the controller
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
YourCell *cell = (YourCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];
[cell buttonTappedBlock:^(id sender) {
NSLog(#"%#", item[#"title"]);
}];
return cell;
}
For further information in blocks, you can read Working With Blocks

Resources