Delegates and Protocols for UIButton toggle in objective c [duplicate] - ios

I have toggle buttons in my tableview cells and I click them on for some cells but when I scroll down, those same buttons are selected for the bottom cells as well even though I didn't select them yet. I know this is happening because of the tableview reusing cells...is there any way I can fix this?
The cells are dynamic, not static.
what the tableview looks like
** EDIT: Also, lemme know if my logic seems alright: I tried creating a mutable array in my viewcontroller class and then setting all it's values to #"0". Then, in my tableviewcell's class, I set the value in the array to #"1" at the index of the current cell if I select the button, so then back in my viewcontroller class, I can tell if I have already selected a button at that cell or not. The only flaw is that I can't access the array in my tableviewcell's class, it is coming out at null...i guess that it because of the mvc pattern in objective c. Any advice?
EDIT
I am still unable to resolve my issue. Can someone please help me? I have been stuck on it for a while now!
I am trying to create a tableview where the cells have a check and cross button and when I click the check button, it should turn green, but the same button in other cells should remain gray, however, when I scroll down, some cells that I didn't select buttons in still turn green...because of cell recycling.
I am using delegates and protocols right now but it isn't working; perhaps I am using it wrong?
I am setting yesChecked value in IBaction functions in my cell class, and in my viewcontroller class, I am using that yesChecked value to see what color to give to the button based on whether it says "yes" or "no".
Kindly help! Thanks!
#protocol DetailsTableViewCellDelegate <NSObject>
- (void) customCell:(DetailsTableViewCell *)cell yesBtnPressed:(bool)yes;
#property (nonatomic, retain) NSString * yesChecked;

You'd have to select or deselect them in cellForRowAt. For example if your cell had a leftButton property and you had a model like this, you could do something like the following:
#interface Model : NSObject
#property (nonatomic, assign) BOOL selected;
#end
#protocol CustomCellDelegate <NSObject>
- (void)cellActionTapped:(UITableViewCell *)cell;
#end
#interface CustomCell : UITableViewCell
#property (nonatomic, assign) BOOL leftButtonSelected;
#property (weak, nonatomic, nullable) id<CustomCellDelegate> delegate;
#end
// ModelViewController.h
#interface ModelViewController : UIViewController<CustomCellDelegate>
#end
// ModelViewController.m
#interface ViewController () {
NSArray<Model*>* models;
}
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"reuseIdentifier"];
((CustomCell *)cell).delegate = self;
((CustomCell *)cell).leftButtonSelected = models[indexPath.row].selected;
return cell;
}
- (void)cellActionTapped:(UITableViewCell *)cell {
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
// Update data source using (maybe) indexPath.row
}

Related

UITableview buttons getting highlighted on scroll

I have toggle buttons in my tableview cells and I click them on for some cells but when I scroll down, those same buttons are selected for the bottom cells as well even though I didn't select them yet. I know this is happening because of the tableview reusing cells...is there any way I can fix this?
The cells are dynamic, not static.
what the tableview looks like
** EDIT: Also, lemme know if my logic seems alright: I tried creating a mutable array in my viewcontroller class and then setting all it's values to #"0". Then, in my tableviewcell's class, I set the value in the array to #"1" at the index of the current cell if I select the button, so then back in my viewcontroller class, I can tell if I have already selected a button at that cell or not. The only flaw is that I can't access the array in my tableviewcell's class, it is coming out at null...i guess that it because of the mvc pattern in objective c. Any advice?
EDIT
I am still unable to resolve my issue. Can someone please help me? I have been stuck on it for a while now!
I am trying to create a tableview where the cells have a check and cross button and when I click the check button, it should turn green, but the same button in other cells should remain gray, however, when I scroll down, some cells that I didn't select buttons in still turn green...because of cell recycling.
I am using delegates and protocols right now but it isn't working; perhaps I am using it wrong?
I am setting yesChecked value in IBaction functions in my cell class, and in my viewcontroller class, I am using that yesChecked value to see what color to give to the button based on whether it says "yes" or "no".
Kindly help! Thanks!
#protocol DetailsTableViewCellDelegate <NSObject>
- (void) customCell:(DetailsTableViewCell *)cell yesBtnPressed:(bool)yes;
#property (nonatomic, retain) NSString * yesChecked;
You'd have to select or deselect them in cellForRowAt. For example if your cell had a leftButton property and you had a model like this, you could do something like the following:
#interface Model : NSObject
#property (nonatomic, assign) BOOL selected;
#end
#protocol CustomCellDelegate <NSObject>
- (void)cellActionTapped:(UITableViewCell *)cell;
#end
#interface CustomCell : UITableViewCell
#property (nonatomic, assign) BOOL leftButtonSelected;
#property (weak, nonatomic, nullable) id<CustomCellDelegate> delegate;
#end
// ModelViewController.h
#interface ModelViewController : UIViewController<CustomCellDelegate>
#end
// ModelViewController.m
#interface ViewController () {
NSArray<Model*>* models;
}
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"reuseIdentifier"];
((CustomCell *)cell).delegate = self;
((CustomCell *)cell).leftButtonSelected = models[indexPath.row].selected;
return cell;
}
- (void)cellActionTapped:(UITableViewCell *)cell {
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
// Update data source using (maybe) indexPath.row
}

Reference to a cell with reuse identifier

I'm developing an iOS-App and therefore I use a UITableViewController. Within "cellForRowAtIndexPath" I use cells with reuse identifiers:
[[[UITableViewCell alloc] initWithStyle:UITableViewCellStyle1 reuseIdentifier:textFieldIdentifier];
The problem is that some cells have a dependecy on each other, e.g. if the user enters text in one cell another cell changes its value.
So what is the best way to safe a reference to the cell that has to be changed? The problem is, that if I safe the reference within "cellForRowAtIndexPath", during the callback for "textFieldDidChange" the reference might be broken, e.g. if the cell is not visible or another cell has the adress due to the reuse identifier?!
Don't try to save references to cached cells. Update whatever you need to display in the table's data source and then call reloadData. That way, the table takes care of refreshing visible cells and dealing with the cache...so you don't need to.
I would make an protocol for the cells
Example
#protocol MyProtocol <NSobject>
- (void) changeText:(NSString)theText;
#end
#interface TableViewCell1 : UITableViewCell
#property (nonatomic, weak) id<MyProtocol> delegate;
#end
#implementation TableViewCell1
//put this in the method where you get the value of the textfield
[self.delegate chageText:#"Hello"];
#end
#interface TableViewCell2 : UITableViewCell <MyProtocol>
#end
#implementation TableViewCell2
- (void) chageText:(NSString *)text {
self.textLabel.text = text;
}
#end

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
}

Visibility between UITableCell and UIViewController

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];

How does UITableViewCell communicate with its UITableView?

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
}

Resources