Unable to flush reusable UITableViewCell data - iphone

I have a UITableView with a custom UITableViewCell.
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//create the cell
MyCell *cell = (MyCell*)[tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
cell.label.text = ..
cell.label2.text = ..
cell.label3.text = ..
Everything works fine, all my data loads properly, etc.
Now, I have a button on this View Controller that opens another view where the user can select which labels to display. So, for instance, display labels 1 and 3, but not 2...
Then, when Done is clicked, I want the tableView to be updated to reflect the new options, but since the cells were loaded with a reuseCellId, no changes are shown. How can I force the cells to recreate?

I thing that the best thing you could do is to store the cells configuration in some kind of structure (a set with the labels indices to be shown would be ok here) and alter this structure with your buttons and reload the table view. Then, on your tableView:cellForRowAtIndexPath: method, you should check that configuration structure in order to know what buttons should be visible.
This code may help:
#interface MyViewController : UIViewController
{
...
NSMutableSet *_labelsToShow;
}
...
#property (nonatomic, retain) NSMutableSet labelsToShow
#end
#implementation MyViewController
#synthesize labelsToShow = _labelsToShow;
- (void)dealloc
{
[_labelsToShow release];
...
}
//you may know which button has to add/remove each label, so this needs to be fixed with your logic
- (IBAction)myButtonAction:(id)sender
{
if (hasToShowLabel)
{
[self.labelsToShow addObject:[NSNumber numberWithInteger:labelIdentifier]];
} else
{
[self.labelsToShow removeObject:[NSNumber numberWithInteger:labelIdentifier]];
}
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"myCell";
MyCustomCell *cell = (MyCustomCell *)[tableView dequeReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault] autorelease];
}
cell.label0.hidden = (![self.labelsToShow containsObject:[NSNumber numberWithInteger:0]]);
cell.label1.hidden = (![self.labelsToShow containsObject:[NSNumber numberWithInteger:1]]);
cell.label2.hidden = (![self.labelsToShow containsObject:[NSNumber numberWithInteger:2]]);
...
return cell;
}
#end
Good luck with this!

This is NOT a good approach
One way you can do this by using different identifier when you want to refresh cells
I am not sure if there is any other better way of doing this.

I solved this issue by just destroying the tableview, and recreating it every time.

Related

Switch from UIView to UITableview on press of a button

I made a button and connected it to an action in interface builder. What do I need to do in the action method to switch from displaying a view to displaying a table view?
Here is some of my code:
// SwitchToTableViewController.h
#import <UIKit/UIKit.h>
#interface SwitchToTableViewController : UIViewController {
}
-(IBAction) switch : (id) sender;
#end
and
//SwitchToTableViewController.m
#import "SwitchToTableViewController.h"
#import "Table.h"
#implementation SwitchToTableViewController
-(IBAction) switch : (id) sender{
// what is i need to write here to switch to tableview
}
#end
and
//table.m
#import <UIKit/UIKit.h>
#interface Table : UITableViewController {
}
#end
and
//table.h
#import "Table.h"
#implementation Table
#pragma mark Table view methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 2;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell...
return cell;
}
#end
You need:
Create an instant of Table.
Pass any data over to this instant, most likely an array so that you can use it to display cells data. You need to create some iVars in your Table class first. Currently you don't have any.
Chose one of many ways to present your Table instant.
Look into Apple's doc. It has quite a bit sample code.
Edit for sample code:
In your SwitchToTableViewController.h file, add this line:
#import "Table.h"
In your SwitchToTableViewController.m, make this changes
-(IBAction) switch : (id) sender{
    // what is i need to write here to switch to tableview
Table *myTable = [[Table alloc]init];
myTable.cellArray = [NSArray arrayWithObjects:#"Item1", #"Item2", #"Item3", nil]; //only an example to be displayed
[self presentViewController:myTable animated:YES completion:nil]; // present your Table - view controller
}
Your Table.h file should look like this:
#import <UIKit/UIKit.h>
#interface Table : UITableViewController {
}
#property (nonatomic, strong) NSArray *cellArray; // adding this line to get data from the parent
#end
In your Table.m, make this changes
#implementation Table
#synthesize cellArray; //add this line right after #implementation Table
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return cellArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.textLabel.text = [cellArray objectAtIndex:indexPath.row];
}
return cell;
}
You have two view controllers here, not two views. It's very important to realise the difference between those two concepts. I've put in bold in the answer below some other key words you need to read up on.
To show another view controller in response to something like a button press, you need to create a new instance of your destination view controller, then get it on the screen somehow.
There are many ways to do this but the simplest way, for a beginner, would be to do all this in storyboards. Add both of your view controllers to the storyboard.
Your initial view controller (with the button) should be embedded in a UINavigationController. Control-drag from the button to your table view controller. This will create a segue. Choose "push" from the list of options.
Hey presto, your table view will now appear on screen when you press the button.
First Create a SingleView Application (UIViewController based) name it as SwitchingView.
Then add a UITableView above the view. Connect using Interface builder. Set delegate and datasource of tableview as the SwitchingView.
Also add a button on the view (before adding table view). Set table view is Hidden using IB(interface builder).
In button Action do
tableview.hidden = NO;
This will show your Table view!!! If you want to show view again, just make
tableview.hidden = YES;
in tableView:didSelectForIndexPathRow method.

Using dequeueReusableCellWithIdentifier with custom UITableViewCell

So I'm unsure how this dequeueReusableCellWithIdentifier works and if what I'm looking for is possible. I have a custom UITableViewCell with a BOOL showIcon. In the TableViewCell, if it's true, I show it, otherwise, I don't show this icon on my cell. In my cellForRowAtIndexPath, I grab the object in my array from my model, and set it for the UITableViewCell property.
This works at first on what is visible on my screen. Then as I scroll downwards on the table, it does not work and the values I should be seeing for the showIcon just don't show. And then when I scroll back to the top, the original icons that were there, are not there. Is the dequeueReusableCellWithIdentifier still what I want to be using in this case? Or am I doing something wrong with setting and showing data in it? Thanks a bunch.
CODE:
On my custom UITableViewCell, I have a
BOOL showIcon;
In my cellForRowAtIndexPath method, I use the UINib way of getting my custom UITableViewCell like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
static NSString *OrderTableViewCellIdentifier = #"OrderTableViewCellIdentifier";
OrderTableViewCell *cell = (OrderTableViewCell *)[tableView dequeueReusableCellWithIdentifier:OrderTableViewCellIdentifier];
if (cell == nil) {
UINib *cellNib = [UINib nibWithNibName:#"OrderTableViewCell" bundle:nil];
[cellNib instantiateWithOwner:self options:nil];
cell = self.TbvCell;
[cell.CheckmarkButton addTarget:self action:#selector(CheckmarkButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
self.TbvCell = nil;
}
Order *order = [orderArray objectAtIndexPath:row];
cell.order = order;
}
Then in my TableViewCell, it's something like this:
#property (nonatomic, retain) Order *order;
#property (nonatomic, retain) UIImageView *icon;
overridden setter:
- (void)setOrder:(Order *)newOrder {
if (!order.showIcon) {
icon.hidden = YES;
}
}
Your code reuse your cell so you have to cover all the case in your setOrder method. Try with:
- (void)setOrder:(Order *)newOrder {
if (!order.showIcon) {
icon.hidden = YES;
}
else {
icon.hidden = NO;
}
}
Or simpler:
- (void)setOrder:(Order *)newOrder {
icon.hidden = !order.showIcon;
}
Yes, dequeueReusableCellWithIdentifier works well with any custom UITableViewCell subclasses. However, keep in mind that this method is for memory saving, so you'll have to do the following (even with not subclassed, normal UITableViewCells):
This method returns an already used UITableViewCell instance (or nil if the table view doesn't have enough cells yet). It means that the cell won't be "empty"; you'll need to clear and re-set all its properties. For example, you'll need to be able do decide from the cell's corresponding NSIndexPath whether its icon has to be displayad, and what icon image you'd like to use at all.
So edit your code like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
static NSString *OrderTableViewCellIdentifier = #"OrderTableViewCellIdentifier";
OrderTableViewCell *cell = (OrderTableViewCell *)[tableView dequeueReusableCellWithIdentifier:OrderTableViewCellIdentifier];
if (cell == nil) {
UINib *cellNib = [UINib nibWithNibName:#"OrderTableViewCell" bundle:nil];
[cellNib instantiateWithOwner:self options:nil];
cell = self.TbvCell;
[cell.CheckmarkButton addTarget:self action:#selector(CheckmarkButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
self.TbvCell = nil;
}
// set your cell's properties to default values.
// e. g.: cell.showIcon = NO; and so on
Order *order = [orderArray objectAtIndexPath:row];
cell.order = order;
// then do a recustomization using the NSIndexPath's -row and -section properties
}
Hope this will help.

Table view with custom cells

I have a tableview with custom cells in my App and each cell contains two checkbox buttons.
The problem is, when a scrolling event is triggered (up or down) it's reloading the tableview. Therefore, the checkbox buttons become to initial state.
Please give me a solution.
Thanks you
You're going to have to maintain a list yourself to determine which cells should be checked or not. Remember that in tableView:cellForRowAtIndexPath:, a proper implementation will recycle cells so that you never have more than 10-15 cells instantiated. This can cause some funky results if you don't handle for it properly. When I've done a poor implementation, I've seen certain cell properties "carry over" from one cell to the next.
Anyway, here's what I'd recommend (based on what I think you're asking):
1. Create a class to back each UITableViewCell
2. Create a property in that class to determine which of the two checkboxes (or neither or both) should be checked.
3. In your ViewController/TableViewController, maintain an NSMutableArray/NSArray where 1 item in the array = 1 cell in the UITableView.
4. In your tableView:cellForRowAtIndexPath: method, get a reference to the appropriate item in your array.
5. Then, check that instance's properties and set the checkbox values appropriately.
Sample Code:
TableView.h
#interface TableView : UITableViewController
#property (strong, nonatomic) NSMutableArray *itemArray;
#end
TableView.m
#implementation TableView
#synthesize itemArray;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Assume you get a valid, custom UITableViewCell at this point (named "cell")
// Configure the cell...
NSObject *classItem = [[self itemArray] objectAtIndex:[indexPath row]];
[[cell checkBox1] setChecked:[classItem checkbox1Checked]];
[[cell checkBox2] setChecked:[classItem checkbox2Checked]];
return cell;
}
#end
You should set the state of the button in the datasource and load this state when creating the cell. I wrote a small Xcode project to demonstrate this.
Well you should not use the TableView as the datasource.
Every time the cell comes into view the UITableViewDataSource is asked for the UITableViewCell at as indexpath.
- (void) tableView:(UITableView *)tableView setImage:(UIImage *)image forCellAtIndexPath:(NSIndexPath *)indexPath
In the is method you should set the checkbox state as it is reflected in your dataSource.
When the checkbox is changed save it in the data source with the selected state.
Example:
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"CheckedTableViewCell";
CheckedTableViewCell *cell = (CheckedTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
[[NSBundle mainBundle] loadNibNamed:cellIdentifier owner:self options:nil];
cell = (CheckedTableViewCell *)self.nibCell;
self.nibCell = nil;
}
item *item = [self objectAtIndexPath:indexPath];
cell.titleLabel.text = item.title;
cell.switch.on = item.selected;
return cell;
}
You could save the state in NSUserDefaults right when you click it. Just add a target with #selector(changedOne: ) and add the void statement:
- (void)changedOne: (id)sender {
NSUserDefaults *df = [NSUserDefaults standardUserDefaults];
NSString *row = [NSString initWithFormat:#"toggleOneRow%i",indexPath.row];
if (sender.on) {
[df setBool:YES forKey:row];
}
else {
[df setBool:NO forKey:row];
}
}
Are you using cellForRowAtIndexPath. If yes, then instead of
static NSString CellIdentifier=#"CellIdentifier"
use
NSString *CellIdentifier=[NSString stringWithFormat=#"CellIdentifier%d",indexPath.row];
Other approach you can take is assign tags to checkboxbuttons and take one dictionary in appDelegate file and set value for checkbox tag.initially you may set is either checked or unchecked.and set the values in cellforrowatindexpath method. set the values of checkboxes as per appdelegate dictionary.and update the state in appdelegate dictionary when user selects or deselects the button.

UITextField in UITableViewCell Help

I have scoured the internet looking for a good tutorial or posting about having a UITableView populated with a UITextField in each cell for data entry.
I want to keep track of each UITextField and the text written within it while scrolling. The tableView will be sectioned. I have been using a custom UITableViewCell but I'm open to any method.
Also, is it possible to use the textFields as ivars?
If any of you could point me in the right direction, it would be greatly appreciated.
Thank you in advance!
To solve your problem you have to maintain an array, with some number (number of textFields you added to all cells) of objects.
While creating that array you need add empty NSString objects to that array. And each time while loading the cell you have to replace the respected object to respected textField.
Check the following code.
- (void)viewDidLoad{
textFieldValuesArray = [[NSMutableArray alloc]init];
for(int i=0; i<numberofRows*numberofSections; i++){
[textFieldValuesArray addObject:#""];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return numberofSections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return numberofRows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
CustomTextField *tf = [[CustomTextField alloc] initWithFrame:CGRectMake(5,5,290,34)];
tf.tag = 1;
[cell.contentView addSubView:tf];
[tf release];
}
CustomTextField *tf = (CustomTextField*)[cell viewWithTag:1];
tf.index = numberofSections*indexPath.section+indexPath.row;
tf.text = [textFieldValuesArray objectAtIndex:tf.index];
return cell;
}
- (void)textFieldDidEndEditing:(UITextField *)textField{
int index = textField.index;
[textFieldValuesArray replaceObjectAtIndex:index withObject:textField.text];
}
First of all, you must understand that UITableViewCell and UITextField are just views, they are not supposed to hold data, they are just supposed to display them and allow the user to interact with them: The data should remain stored in the controller of the table view.
You have to remember that UITableView allows you to reuse UITableViewCell instances for performance purpose: what's displayed on the screen are actually the only subviews UITableView keep there. It means that you'll reuse one cell that already has a text field in it and set the text on that field directly. When the user will tap on the field it will edit it and you'll have to get the value back from it when the user will have finished.
The fastest way, would be to use what Satya proposes, that is building normal UITableViewCell and insert into a UITextField (there's no need for a CustomTextField class...). The tag will allow you to get back to the text field easily... But you'll have to setup your text field so it behaves properly when the table view resizes or if a label in the same cell changes.
The cleanest way to do that is to subclass UITableViewCell and setup the layout of your label and text field, and you can provide the text field as a property of the custom subclass.
I have used Textfields in tableview for data entry.
I have customised the UITextField class in a separate class called Utility :
In Utility.h
#interface CustomUITextField:UITextField{
NSInteger rowNumber;
}
In Utility.m
#implementation CustomUITextField
#synthesize rowNumber;
#end
My tableView cellForRowAtIndexPath method is
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *Identifier = #"Cell";
UITableViewCell *cell = [theTableView dequeueReusableCellWithIdentifier:Identifier];
if(cell == nil)
cell = [self reuseTableViewCellWithIdentifier:Identifier withIndexPath:indexPath];
CustomUITextField *itemNameTextField = (CustomUITextField *)[cell.contentView viewWithTag:TEXTFIELD_TAG];//this is the tag I have set in reuseTableViewCellWithIdentifier method for textfield
itemNameTextField.rowNumber = indexPath.row;
itemNameTextField.text = #"";//you can set it for the value you want
if(itemListTable.editing)
itemNameTextField.borderStyle = UITextBorderStyleRoundedRect;
else
itemNameTextField.borderStyle = UITextBorderStyleNone;
return cell;
}
You can customise the delegate methods of UITextField for CustomUITextField & can save the text entered in a particular row's textfield by accessing the CustomTextField's row number.
Just try with this.
I had the same problem here is some code i found that treats this problem . it puts the data enterd in a Array Look at the Debugger console to see the results of the text being typed here's the link TextFieldCell. . Happy Codeing

How to populate a label field from a selected row in a uitableview

I have a uitableview that is populated from a sqlite query.
I want to select or click on a row and then display that row's value in a uilabel field. To show the user that the row was selected.
I also want to pass that value on to different controllers that will be called later.
Here is a copy of my cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"psystem";
PSystem *psystem = [self.ppdm_systems objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell...
// self.accessoryType = UITableViewCellAccessoryCheckmark;
cell.textLabel.text = psystem.system_id;
return cell;
}
I took out the _label.text .... in my various experiments.
Now what is not working is the passing of the value to different controllers.
Using the example listed here, the source controller is TableViewController and is where the value is set. The target controller is DetailViewController.
I can pass the title of the tab bar in, but that's from TableView --> DetailView.
I am not sure how to pull from tableview; ie: Tableview <-- DetailView when I am in DetailView.
thx
In your UIViewController, implement:
- (MyObject *)valueForSelectedRow {
MyCell *cell = (MyCell *)[self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]];
return cell.myObject;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Get value
MyObject *object = [self valueForSelectedRow];
// Update the label, assumed that _label is a pointer to a UILabel view object.
_label.text = object.myValue;
}
When you want to push a new view controller, you just call -valueForSelectedRow and then use that value to push the controller.
This is assumed that you have a UITableViewCell subclass, with a property set to some model object. When you don't have that and just set the text property, that NSString object will be your 'model' object, although it would be easier when your cells handle custom model objects.
EDIT: Thanks for editing your answer. I now have the information I need. In this line: cell.textLabel.text = psystem.system_id, you setup the cell by simply setting the textLabel's text property. This is what I described in the paragraph above. I always create a UITableViewCell subclass, with a property set the the complete PSystem object. When you assign a PSystem object to the cell, it will handle it's contents, so you can easily manage your view in the, well, view. That's a very compelled approach since you never have to look at the controller again to alter the view's contents.
However, it can be done the way you currently have it. It would look something like:
- (NSString *)valueForSelectedRow {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]];
return cell.textLabel.text;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Get value
NSString *value = [self valueForSelectedRow];
// Update the label, assumed that _label is a pointer to a UILabel view object.
_label.text = value;
}
In this case, your PSystem model has been replaced with an NSString object. For this, it's enough, but it could be so much easier to have the object itself. Okay, that can also be done by selecting the PSystem object again from the p_system array by the NSIndexPath, but things will become harder once you come up with more complex tableviews.