How to save "state" of of UITableViewCell - ios

using Objective-C
I have a table with cell that can be swiped (as example use this post with some changes in logic). Currently i can swipe cell and after flipping table it's "state" will be reset to "closed". I want to implement some "memory" mechanism for storing last state of each cell.
How I try to do it - in cell class create protocol, where implement 2 methods for getting current indexPath of cell and adding it to the NSMutableSet, and before presenting it, controller check if this cell already in collection - set required state.
I use separate .xib files for both viewController with tableView and tableViewCell.
Code - cell.h class file:
#protocol PEDoctorsViewTableViewCellDelegate
#optional
//buttonAction
- (void)buttonDeleteAction;
//method for closing cell with swipe
- (void)cellDidSwipedIn: (UITableViewCell*)cell;
//method for opening cell with swipe
- (void)cellDidSwipedOut:(UITableViewCell *)cell;
#end
#interface PEDoctorsViewTableViewCell : UITableViewCell <PEDoctorsViewTableViewCellDelegate>
#property (strong, nonatomic) id <PEDoctorsViewTableViewCellDelegate> delegate;
//method for protocol PEDoctorsViewTableViewCellDelegate
- (void)setCellSwiped;
#end
cell.m file
...
//add some code for handling reuse of cells
//fixing issue with not remembeering of cell's state
//close all cells during flipping tableView
- (void)prepareForReuse{
[super prepareForReuse];
//close all cells before showing
self.viewDoctorsNameView.frame = CGRectMake(0, 0, self.viewDoctorsNameView.frame.size.width, self.viewDoctorsNameView.frame.size.height);
}
And viewController with tableView class.m
#pragma mark - PEDoctorsViewTableViewCellDelegate
- (void)cellDidSwipedOut:(UITableViewCell *)cell{
NSIndexPath * currentOpenedCellIndexPath = [self.tableView indexPathForCell:cell];
[self.currentlySwipedAndOpenesCells addObject:currentOpenedCellIndexPath];
}
- (void)cellDidSwipedIn:(UITableViewCell *)cell{
[self.currentlySwipedAndOpenesCells removeObject:[self.tableView indexPathForCell:cell]];
}
- (void)buttonDeleteAction {
NSLog(#"delete action");
}
and check in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method like
if ([self.currentlySwipedAndOpenesCells containsObject:[self.tableView indexPathForCell:cell]]){
[cell setCellSwiped];
}
also this class have
//place to store set of opened cells
#property (strong, nonatomic) NSMutableSet * currentlySwipedAndOpenesCells;
initialized in - (void)viewDidLoad.
As result - i got all cells with reset position.
I try to change code in - (void)prepareForReuse method - position of cell changes according to my changes in method - this part works fine.
Try to check delegate method - all fire in exactly required moment.
Try to add some NSLog method before adding object in NSMuatbaleSet and before cell will be recreated (think maybe data lost) - result - during adding object in to set - all looks like OK:
During recreating - looks like OK too :
but not changes in table shown. I try to check what object are captured during saving -
and after small flip for starting recreating process got next:
as I understand - same cell now a new object. Also try to add some BOOL property to cell for controlling and comparing it's state - result the same - each time new cell.
Any idea what i'm doing wrong?

Related

add table cells to class that inhiret UITableViewController

I have the following header file
#interface Menu : UITableViewController;
and the following m file
#import "Menu.h"
#implementation Menu
+ (void)initialize {
// TODO here I want to add cells to my table view
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)newIndexPath{
[tableView deselectRowAtIndexPath:newIndexPath animated:YES];
//my logic goes here
}
#end
I want to be able to add cells to my tableview in initialize method, how can I do that ??
To add cells to you table, you must implement the delegate and data source methods.
- tableView:cellForRowAtIndexPath: Will create and return a cell for the specified index path, which itself is basically a section and row number.
- tableView:numberOfRowsInSection: Specifies the number of cells to be in the table.
You can see the documentation here.
There is a good tutorial here.

How can I allocate and initialize a custom UITableViewCell NOT in cellForRowAtIndexPath?

I won't go into the WHY on this one, I'll just explain what I need.
I have a property in my implementatioon file like so...
#property (nonatomic, strong) MyCustomCell *customCell;
I need to initialize this on viewDidLoad. I return it as a row in cellForRowAtIndexPath always for the same indexPath, by returning "self.customCell".
However it doesn't work, I don't appear to be allocating and initializing the custom cell correctly in viewDidLoad. How can I do this? If you know the answer, save yourself time and don't read any further.
Ok, I'll put the why here in case people are curious.
I had a table view which was 2 sections. The first section would always show 1 row (Call it A). The second section always shows 3 rows (Call them X, Y, Z).
If you tap on row A (which is S0R0[Section 0 Row]), a new cell would appear in S0R1. A cell with a UIPickerView. The user could pick data from the UIPickerView and this would update the chosen value in cell A.
Cell X, Y & Z all worked the same way. Each could be tapped, and they would open up another cell underneath with their own respective UIPickerView.
The way I WAS doing this, was all these cell's were created in the Storyboard in the TableView, and then dragged out of the View. IBOutlets were created for all. Then in cellForRAIP, I would just returned self.myCustomCellA, or self.myCustomCellY.
Now, this worked great, but I need something more custom than this. I need to use a custom cell, and not just a UITableViewCell. So instead of using IBOutlets for all the cells (8 cells, 4 (A,X,Y,Z) and their 4 hidden/toggle-able UIPickerView Cell's), I created #properties for them in my implementation file. But it's not working like it did before with the standard UITableViewCell.
I'm not sure I'm initializing them correctly? How can I properly initialize them in/off viewDidLoad so I can use them?
.m
#interface MyViewController ()
#property (strong, nonatomic) MyCustomCell *myCustomCellA;
...
viewDidLoad
...
self.myCustomCellA = [[MyCustomCell alloc] init];
...
cellForRowAtIndexPath
...
return self.myCustomCellA;
...
If only I understood your question correctly, you have 3 options:
I would try really hard to implement table view data source with regular dynamic cells lifecycle in code and not statically – this approach usually pays off when you inevitably want to modify your business logic.
If you are certain static table view is enough, you can mix this method with overriding data source / delegate methods in your subclass of table view controller to add minor customisation (e.g. hiding certain cell when needed)
Alternatively, you can create cells using designated initialiser initWithStyle:reuseIdentifier: to instantiate them outside of table view life cycle and implement completely custom logic. There is nothing particular that you should do in viewDidLoad, that you wouldn't do elsewhere.
If you have a particular problem with your code, please post a snippet so community can help you
I suggest you to declare all your cells in storyboard (with date picker at right position) as static table and then override tableView:heightForRowAtIndexPath:
Define BOOL for determine picker visibility and its position in table
#define DATE_PICKER_INDEXPATH [NSIndexPath indexPathForRow:1 inSection:0]
#interface YourViewController ()
#property (assign, nonatomic) BOOL isPickerVisible;
#end
Then setup initial value
- (void)viewDidLoad {
[super viewDidLoad];
self.isPickerVisible = YES;
}
Override tableView delegate method
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:DATE_PICKER_INDEXPATH] && !self.isPickerVisible) {
return 0;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
And finally create method for toggling picker
- (void)togglePicker:(id)sender {
self.isPickerVisible = !self.isPickerVisible;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
which you can call in tableView:didSelectRowAtIndexPath:
According to your problem, you can create pairs (NSDictionary) of index path and bool if its visible and show/hide them according to that.
Here's what I was looking for:
MyCustomCell *cell = (MyCustomCell *)[[[UINib nibWithNibName:#"MyNibName" bundle:nil] instantiateWithOwner:self options:nil] firstObject];

Events when clicks on UITableViewCell in objective C?

I encountered an issue in my project as below:
I wrote a class that is subclass of UIView with:
1 - a button
2 - a table view
3 - a textfield
I've created an object of that class and add the object to an view of uiviewcontroller. When I click on the button, make the table view appear with a list of all countries and click on a cell of table view, the textfield will get cell's value. I wanna detect events when I touch on a cell of tableview. How can I do that? Please help me, Thank in advance.
P/S: tableview is subView of contentView. Hierarchy as follow:
ViewController - ScrollView - contentView -(View1, View2...)
Is this what you're trying to do?
You didn't give codes, or something.. So, this is just my assumption of what you're trying.. hmm..
Please give us more details... edit your question if necessary..
//viewController.h
#interface viewController : UIViewController
#property (nonatomic) NSMutableArray *selectedIndexs;
// use this array as your dataSource for the secondTableView (popup tableView?)
#end
//viewController.m
#implementation viewController
// codes..
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
/*
you need to store the countries indexPath to cell the name/details
in this example i will just get the `.textLabel.text`
*/
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath/*current selected indexPath*/];
if ([self.selectedIndexs containsObject:cell.textLabel.text])
{
// [self.selectedIndexs removeObject:cell.textLabel.text]; ]> delete if already in the array
// return; ]> if you dont want to remove the object just return..
}
else
[self.selectedIndexs addObject:cell.textLabel.text]; // add the country if not exist in the array
}
- (void)showSelectedCountries:(UIButton *)sender //this is the number 1 from the image (Button)
{
UIView *customViewForSelected = [[UIView alloc] init];
// then if you have declared a property for `dataSource` inside your 'customViewForSelected' just:
customViewForSelected.yourSelectedsDataSource = selectedIndexs;
// then show the view with the table inside which contains only the table.. correct?
[self.view addSubview:customViewForSelected]; // i just added as subview because i dont know how you call it..
}
#end
Hope, you find this helpful.. Cheers..
I created a class: DropdownListViewCountries is subclass of UIView and include a button, a text field and a tableview.
#interface DropdownListViewCountries : UIView<UITextFieldDelegate, UITableViewDelegate>
.In #implementation DropdownListViewCountries, I implemented didSelectRowAtIndexPath
and then I created class: IndividualPersonalData is subview of UIViewController. This class contains a progress view bar.
And then I created an object of DropdownListViewCountries and add to IndividualPersonalData.
How to I touch on a cell of tableview to change progress view value?
UITableView had delegate protocol, and for your needs, you only have to implement
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
You will know where you tap by indexPath, so you know the location, then you can do whatever you want.
If you want to get the corresponding cell, use
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
Simply pass indexPath from didSelectRowAtIndexPath to cellForRowAtIndexPath
Like people said in the comments, all you need is in the delegate and data source. If you're doing it all programmatically you need to add many things to your code such as <UITableViewDelegate, UITableViewDataSource> and also call them on viewDidLoad. Then you need to implement some methods. Apple has a vast documentation on all classes and methods, take a look at them. You need the didSelectRowAtIndexPath.
Hope I could help
Thanks you all for your help. Now for my purpose I found below code to detect events:
[self.firstDropdownListView.countriesTextField addObserver:self forKeyPath:#"text" options:0 context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:#"text"] && object == self.firstDropdownListView.countriesTextField)
{
// text has changed
LogDebug(#"HELLO WORLD");
}
}

If a UITableViewCell has multiple buttons, where is the best place to handle touch events

I'm presenting a lot of data in format of a table with multiple columns. Almost each column has a button (up to 4 in total) and each row is a UITableViewCell.
How could I detect that the buttons were touched and where should I handle touch events of the buttons? I'm certain, it shouldn't be a didSelectRowAtIndexPath method though.
As soon as, I could detect which button was pressed, I would fetch the data in that particular row and manipulate it. So, I need to know the indexPath of the row, as well as what button was pressed on it.
You can subclass UIButton with two properties row and column and implement the logic below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"
forIndexPath:indexPath];
MyButton *button1 = (MyButton *)[cell viewWithTag:1];
button1.row = indexPath.row;
button1.column = 1; // view tag
[button1 addTarget:self
action:#selector(clickAction:)
forControlEvents:UIControlEventTouchUpInside];
// button2,button3...
return cell;
}
-(void)clickAction:(MyButton *)sender {
// now you can known which button
NSLog(#"%ld %ld", (long)sender.row, (long)sender.column);
}
Generalized undetailed answer:
Create UITableviewcell subclass, link cell ui elements to this class.
Add method configureWithModel:(Model*)model; //Model being the information you want the cell to represent
Manipulate that information or
If you need to manipulate the screen or other objects. You need to give the table view cell subclass a reference to the other objects when the cell is created. (in code or in storyboard or in nib).
how to handle button presses in ios 7: Button in UITableViewCell not responding under ios 7 (set table cell selection to none)
how to link a button: http://oleb.net/blog/2011/06/creating-outlets-and-actions-via-drag-and-drop-in-xcode-4/
If those four views are UIButton then you will receive the tap events on each button or if they are not UIButton then you should add UITapGestureReconiser on each of this views
Several options here. But I would do the following:
Adopt a Delegate Protocol in your custom cell class (see here: How to declare events and delegates in Objective-C?) . This will handle the target selector for the buttons. Pass this message back to your view controller with the sender. To detect which cell it was in do the following:
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
CGRect senderFrame = CGRectMake(buttonPosition.x, buttonPosition.y, sender.frame.size.width, sender.frame.size.height);
From here you can decide what the do. Use the buttons .x coordinate to determine which button it was or specify a different tag for each button in cellForRowAtIndexPath:. Or if you want to grab the index path of the cell you can do:
NSArray *indexPaths = [YOUR_TABLE_VIEW indexPathsForRowsInRect:senderFrame];
NSIndexPath *currentIndexPath = [indexPaths lastObject];
Because each button has a different action, the only thing you need to get at runtime is the indexPath of the button. That can be done by looking at the button's superviews until a cell is found.
- (IBAction)action1:(UIButton *)button
{
UITableViewCell *cell = [self cellContainingView:button];
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
MyDataModel *object = self.objects[indexPath.row];
// perform action1 on the data model object
// Now that the data model behind indexPath.row was done, reload the cell
[self.tableView reloadRowsAtIndexPaths:#[indexPath]
withRowAnimation: UITableViewRowAnimationAutomatic];
}
- (id)cellContainingView:(UIView *)view
{
if (view == nil)
return nil;
if ([view isKindOfClass:[UITableViewCell class]])
return view;
return [self cellContainingView:view.superview];
}
There: no delegates, no tags, and the action doesn't care about the internals of the cell.
You will still want to subclass UITableViewCell with the four buttons (call them button1, button2, button3, and button4 if you don't have better names). You can make all the connection is Interface Builder. This will only be needed for populating object data into the cell during -tableView:cellForRowAtIndexPath:.
Ideally, you should create a custom cell by subclassing UITableViewCell and implement the actions for each of these buttons in that cell. If your view controller needs to know about these actions, you can define a protocol, MyCustomCellDelegate or similar, and have your view controller conformed to that protocol. Then MyCustomCell will be able to send messages to the view controller when user interacts with its buttons or other controls.
As in the example code below, you can create a cell in storyboard or nib and hook one of the button's action to firstButtonAction method of CustomTableCell class.
Also, you need to set your view controller as delegate property of CustomTableCell object created and implement the method buttonActionAtIndex: of CustomTableCellDelegate in your view controller class. Use controlIndexInCell param passed to this method to determine which button might have generated the action.
#protocol CustomTableCellDelegate <NSObject>
- (void) buttonActionAtIndex:(NSInteger)controlIndexInCell
#end
In CustomTableCell.h class
#interface CustomTableCell: UITableViewCell
#property (nonatomic, weak) id <CustomTableCellDelegate> delegate
- (IBAction) firstButtonAction:(id)sender
#end
In CustomTableCell.m class
#implementation CustomTableCell
#synthesize delegate
- (IBAction) firstButtonAction:(id)sender{
if ([delegate respondToSelector:#selector(buttonActionAtIndex:)])
[delegate buttonActionAtIndex:0];
}
#end
This is a personal preference on how I like to handle situations like these, but I would first subclass UITableViewCell because your table cells do not look like a default iOS UITableViewCell. Basically you have a custom set up, so you need a custom class.
From there you should set up your 4 IBActions in your header file
- (IBAction)touchFirstButton;
- (IBAction)touchSecondButton;
- (IBAction)touchThirdButton;
- (IBAction)touchFourthButton;
You do not need to pass a sender in these actions, because you will not be using that object in these methods. They are being created to forward the call.
After that set up a protocol for your UITableViewSubClass
#protocol UITableViewSubClassDelegate;
Remember to put that outside and before the #interface declaration
Give your sell a delegate property
#property (nonatomic, strong) id<UITableViewSubClassDelegate> delegate;
and finally define your actual protocol, you will need to set up 4 methods, 1 for each button and take your subclass as a parameter
#protocol UITableViewSubClassDelegate <NSObject>
- (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedSecondButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedThirdButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedFourthButtonWithCell:(UITableViewSubClass*)cell;
#end
This will be placed outside of the #interface #end section at the bottom
After that create a configureWithModel: method in your #interface and #implementation as well as a property for your model
#interface:
#property (nonatomic, strong) Model *model;
- (void)configureWithModal:(Model*)model;
#implementation:
- (void)configureWithModal:(Model*)model {
self.model = model;
// custom UI set up
}
From here you should configure your action methods in your #implementation file to call the delegate methods, i'm only showing the first one, but you would do this with all of the IBActions
- (void)configureWithModal:(Model*)model {
[self.delegate forwardFirstButtonWithCell:self];
}
From here your custom cell set up is done and we need to go back to the UIViewController that is displaying the UITableView. First go into the header file of the view controller, and import your custom UITableViewCellSubClass and then setup the class to implement this protocol.
It should look something like this
#interface MYViewController : UIViewController <UITableViewSubClassDelegate>
from there you should go into your cellForRowAtIndexPath: method and configure your custom UITableViewCell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCellSubClass *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
cell.delegate = self;
Model *cellModel = self.tableData[indexPath.row];
[cell configureWithModel:cellModel];
return cell;
}
Now go into your cell class and copy paste all of the protocol methods into your viewController class. I will display one as an example.
In your UIViewController:
- (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell {
Model *cellModel = cell.model;
// do stuff with model from outside of the cell
}
do that for all methods and you should be good.
Remember to have all your #imports in so there's no forward declarations and remember to link up the IBActions to your storyboard or xib files. If you want a custom xib for your table cell you will have to check if the cell is nil and then allocate a new one, but if you are using prototype cells then this should be sufficient.
For simplicity sakes i put forwardFirstButtonWithCell: but i would encourage making the name something that describes what it's doing such as, displayPopOverToEnterData or something similar. From there you could even change the parameters of the delegate protocol methods to take models instead so instead of
- (void) displayPopOverToEnterDataWithCell:(UITableViewSubClass*)cell;
make it
- (void) displayPopOverToEnterDataWithModel:(Model*)model;
but, i don't know what type of information you need to access from the cell. So update these methods as you see fit.

Custom Accessory View on UITableView Prototype Cell Not Firing

I have a custom accessory view (a UIButton) on my prototype cell in a storyboard. When I click that button the delegate method for accessory button tapped isn't called. If I use a standard disclosure button it is called just fine. I'm assuming I'm missing a hook up somewhere. Anybody know where?
Guess 1:
You don't really have a "custom accessory view" you have a UIButton which is placed where an accessory would go. This wouldn't fire the delegate accessory tapped button because it's not really an accessory.
Guess 2:
You do have a real accessory view, but it's never getting the "tap" event because you have a UIButton in it which is eating the user interaction but not firing an action. In that case try adding a simple UIView instead.
Other than that, I'd need more information.
apple docs for the accessory delegate method says
The delegate usually responds to the tap on the disclosure button (the accessory view) by displaying a new view related to the selected row. This method is not called when an accessory view is set for the row at indexPath.
so it is not called for custom accessory views. it is a standard behaviour.
I ended up working around this using a protocol for custom table cells.
CustomAccessoryTableCellDelegate.h
#protocol CustomAccessoryTableCellDelegate <NSObject>
#required
- (void) accessoryButtonTappedForCell: (UITableViewCell *) cell;
#end
CustomAccessoryViewTableCell.h
#interface CustomAccessoryViewTableCell : UITableViewCell
/** The custom accessory delegate. */
#property (nonatomic, weak) id<CustomAccessoryTableCellDelegate> delegate;
#end
CustomAccessoryViewTableCell.m
- (IBAction) buttonAction:(id)sender{
if ([self.delegate respondsToSelector:#selector(accessoryButtonTappedForCell:)]) {
[self.delegate accessoryButtonTappedForCell:self];
}
}
Inside Table View Controller
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
CustomAccessoryViewTableCell *cell = [tableView
dequeueReusableCellWithIdentifier:#"MyCustomCell"];
cell.label.text = #"Some Name";
cell.delegate = self;
return cell;
}
#pragma mark - Custom Accessory View Delegate
- (void) accessoryButtonTappedForCell:(UITableViewCell *)cell{
[self tableView:self.writerTable
accessoryButtonTappedForRowWithIndexPath: [self.writerTable
indexPathForCell:cell]];
}

Resources