I'm trying to hide a label in my TableViewCell when I go into "editing mode". So I created a protocol:
// UITableView Class
#protocol CellDelegate <NSObject>
- (void)toggleOpacityOff
- (void)toggleOpacityOn
#end
//in #interface
#property (nonatomic, strong) id <CellDelegate> delegate;
I implemented these methods in the UITableViewCell class and passed the delegate to the cell class:
// cellForRowAtIndexPath method
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
self.delegate = cell;
return cell;
But when I run this the methods apply only to the top most cell in the TableView.
Why is that and how can I fix it? Many thanks! :)
You should be able to fix this by changing the following line to set the correct delegate (remember that delegates should always be weak, not strong!):
self.delegate = cell;
to line:
cell.delegate = self;
The longer explanation is that delegate would not be the correct way to achieve this.
Delegates are meant to message back some specific action or information, this way your cell would actually tell the delegate that it had something togged on or off. This makes no sense, because you are looking to toggle something off.
The correct way of achieving your goal is to create a UITableViewCell subclass and implement those methods on a cell (toggleOpacityOff and toggleOpacityOn). Or even better create a specific cell property and override it's setter. Then you call this method in UITableViewDelegate method tableView:didSelectRowAtIndexPath:. See the following example:
#interface MyCell : UITableViewCell
#property (nonatomic) BOOL opacity;
#end
#implementation MyCell
- (void)setOpacity:(BOOL)opacity
{
if (opacity)
{
// Set opacity to ON
...
}
else
{
// Set opacity to OFF
}
}
#end
To loop through all cells:
- (void)updateCells
{
for (NSInteger i = 0; i < [self tableView:self.tableView numberOfRowsInSection:0]; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
MyCell *cell = (MyCell *)[self tableView:self.tableView cellForRowAtIndexPath:indexPath];
cell.opacity = YES;
}
}
In addition to this, it might be wise to remember this state in your model, so it remains correct when cells are reused (when scrolling).
Just keep some BOOL in your tableview class like isInEditMode. Then you add something like this into your cellForRowAtIndexPath
if (isInEditMode)
[cell.myText setHidden:YES];
else
[cell.myText setHidden:NO];
And once you want to switch into edit mode you just do this
self.isInEditMode = YES;
[self.tableview reloadData]
Related
I added this code to my ViewController class under (#pragma mark - UITableViewDelegate Methods)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"selected cell = %#", cell.textLabel.text);
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
NSLog(#"row = %ld", indexPath.row);
}
I just want to display the text that I select but it's not working. Any ideas what I'm doing wrong?
Full code is here: IOS 8 Objective-C Search bar and Table view Using Google Autocomplete
Thanks for the help!
EDIT:
I also added the following code:
viewDidLoad method (ViewController.m)
_tableView.allowsSelectionDuringEditing = YES;
_tableView.delegate = self;
_tableView.dataSource = self;
changed ViewController.h code from
#interface ViewController : UIViewController
to
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
in viewDidLoad set the delegates
_table.delegate = self;
_table.dataSource = self;
in the interface then declare the protocols
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
[tableView reloadRowsAtIndexPaths:]... only works when wrapped inbetween [tableView beginUpdates] and [tableView endUpdates];
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
check if the delegate is set to self
_tableView.delegate = self;
Use the debugger and break points.
I would put a break point in:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
and see if the program stops there or not.
Have you set this properties in Interface Builder?
Adding the method is not enough. You should set your 'delegate' property of table view to the 'self' where 'self' means your view controller.
For example in your .m file:
#interface MyViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, weak) IBOutlet UITableView *tableView;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
}
...
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
...
}
EDIT: Also please make sure you have connected the tableView property with a table view object inside of Interface Builder.
Method name changed or wrong. You can drag-drop to set the ViewController as the delegate. But this method needed to process when row touched:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("section: \(indexPath.section)")
print("row: \(indexPath.row)")
// caller is set to the calling ViewController, when it "prepare" to launch this one
caller.payFromSelection( selectedIndex: indexPath.row )
}
Back in the main controller, it gets the value and closes the child vc having the tableview...
func payFromSelection( selectedIndex:Int ) -> Void {
print( selectedIndex )
self.navigationController?.popViewController(animated: true)
//self.navigationController?.popToRootViewController(animated: true)
}
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'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.
I have a custom UITableViewCell, which have a button on it, IB linked to a function called:
- (IBAction)clickUse:(id)sender;
In this function, I planned to pass an object from UITableView's data source (an object in a NSMutableArray) to next UIViewController, when the user clicks the button on the UITableViewCell.
I set a property in the custom UITableViewCell, like this:
#property (nonatomic, retain) SomeObject *some_object;
In UITableView's cellForRowAtIndexPath function, I pass the object to the cell:
MyCustomCell *cell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
cell.some_object = [self.cellData objectAtIndex:indexPath.row];
At this moment I track the object, it's still here. But in the MyCustomCell cell, the object is gone and assigned to nil. Therefore, the object cannot be passed to next UIViewController.
What did I miss?
Perhaps it's better to use a different approach. You can give each cell button a tag. The tag value could be the row index path.
Your -tableView:cellForRowAtIndexPath: method could include the following:
MyCustomCell *cell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
cell.button.tag = indexPath.row
And your -clickUse: method could look like this:
- (IBAction)clickUse:(id)sender
{
UIButton *button = (UIButton *)sender;
SomeObject *object = [self.cellData objectAtIndex:button.tag];
// do stuff with your object on click
}
I recommend creating a delegate protocol to handle this. Define a delegate on the cell. In your cellForRowAtIndexPath: method, set the cell.delegate to the viewController that implements that method. Make sure to nil your delegate in your cell's dealloc and prepareForReuse methods. In my opinion, this is the solution that sets up the cleanest relationships between the objects involved. See example below.
Assigning a button target to an object that is some object other than the superview of the button always seems counterintuitive to me. Or, whenever I work on a codebase where there's a setup like that I find that it eventually gets in the way / confuses things.
Inside CommentCell.h:
#class Comment;
#class SMKCommentCell;
#protocol SMKCommentCellDelegate <NSObject>
#required
- (void)commentCellDidTapShowReplies:(SMKCommentCell *)cell;
- (void)commentCellDidTapUsername:(SMKCommentCell *)cell;
#end
#interface SMKCommentCell : UITableViewCell
#property (nonatomic, strong) Comment *comment;
#property (nonatomic, weak) id<SMKCommentCellDelegate> delegate;
#end
Inside CommentCell.m:
#pragma mark - Actions
- (IBAction)didTapShowReplies:(id)sender
{
if ([self.delegate respondsToSelector:#selector(commentCellDidTapShowReplies:)])
{
[self.delegate commentCellDidTapShowReplies:self];
}
}
- (IBAction)didTapUsername:(id)sender
{
if ([self.delegate respondsToSelector:#selector(commentCellDidTapUsername:)])
{
[self.delegate commentCellDidTapUsername:self];
}
}
Inside your viewController.m:
#pragma mark - SMKCommentCell Delegate
- (void)commentCellDidTapShowReplies:(SMKCommentCell *)cell
{
// Do something
}
- (void)commentCellDidTapUsername:(SMKCommentCell *)cell
{
// Do something
}
Inside cellForRowAtIndexPath:
commentCell.comment = comment;
commentCell.delegate = self;