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..
Related
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
}
}
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'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
I'm figuring out the right mechanism to pass data from UITableViewCells to a UIableViewController (or UIViewController).
Searching within stackoverflow I found different ways to do this and finally I found a mechanism that works well but I don't know if it could be a valid approach.
This is the scenario. First, I created a custom cell (associated with a xib interface), named DataTableViewCell, that extends UITableViewCell. This cell has some outlet to display (and modify) data and an addictional property called index like the following:
#property (nonatomic, copy) NSIndexPath* index;
This property is refreshed inside the method cellForRowAtIndexPath method within the UITableViewController:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
DataTableViewCell *cell = (DataTableViewCell*)[tv dequeueReusableCellWithIdentifier:kCellTableIdentifier];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"DataTableViewCell" owner:self options:nil];
cell = (DataTableViewCell*)self.dataTableViewCellOutlet;
}
// configure the cell with data
// do stuff here...
// configure the cell with the current indexPath
cell.index = indexPath;
return cell;
}
Since it is possible to change values within a cell, I had the need to pass data to the UITableViewController for updating the model. To do that I decided to use a delegate mechanism. So, I created a protocol with a method like the following:
- (void)updateData:(DataItem*)dataItem atIndexPath:(NSIndexPath*)index;
The UITableViewController implements that protocol. In this way, within the cell (against to some events), I can call that method and update the model in the correct way.
Having said this, I have some questions to ask:
Is this a right mechanism to pass data from a cell to a controller?
Is it correct to using an index property like the one use in the cell?
Is it the same using retain policy instead of copy policy. What could be the difference?
Since the solution I found could be very scheming, is it possible to use block insteads?
About blocks, I thought this way:
Within the cell create a property block like the following:
#property (nonatomic, copy) void (^updateModelOnEvent)(DataItem* dataItem);
Then inside the method cellForRowAtIndexPath method within the UITableViewController assign that property to a block code like this (this code is at the same level of cell.index = indexPath;):
// configure the cell with the current indexPath
cell.updateModelOnEvent = ^(DataItem* dataItem) {
[self.model insertObject:dataItem atIndex:indexPath.row];
};
Could be a valid alternative? In this case, do I have to use copy or retain policy?
Thank you in advance.
Why not just use [UITableView indexPathForCell:] with a delegate?
MyViewController.h
#interface MyViewController : UITableViewController <MyTableViewCellDelegate>
#end
MyViewController.m
#implementation MyViewController
// methods...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *reuseIdentifier = #"MyCell";
MyTableViewCell *cell = (id)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil)
cell = [[[MyTableViewCell alloc] initWithMyArgument:someArgument reuseIdentifier:reuseIdentifier] autorelease];
[cell setDelegate:self];
// update the cell with your data
return cell;
}
- (void)myDelegateMethodWithCell:(MyTableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// update model
}
#end
MyTableViewCell.h
#protocol MyTableViewCellDelegate;
#interface MyTableViewCell : UITableViewCell
#property (assign, nonatomic) id <MyTableViewCellDelegate> delegate;
- (id)initWithMyArgument:(id)someArgument reuseIdentifier:(NSString *)reuseIdentifier;
#end
#protocol MyTableViewCellDelegate <NSObject>
#optional
- (void)myDelegateMethodWithCell:(MyTableViewCell *)cell;
#end
MyTableViewCell.m
#implementation MyTableViewCell
#synthesize delegate = _delegate;
- (id)initWithMyArgument:(id)someArgument reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
if (self) {
// custom setup
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
self.delegate = nil;
}
- (void)someActionHappened {
if ([self.delegate respondsToSelector:#selector(myDelegateMethodWithCell:)])
[self.delegate myDelegateMethodWithCell:self];
}
#end
To modify cells you should modify data model and reload table data. Nothing else.
Not necessary to have a indexPath for cell
In your case it is the same using retain or copy policy. Copy makes new objects with same state.