Reuse of table cells issue - iphone

I believe I am incorrectly implementing cellForRowAtIndexPath. I have a UISlider that is hidden by default and appears when a button is pressed in the table cell. When I press the button in the first cell,the slider appears not only in the first cell, but in every third cell when I scroll down. I am currently avoiding this by reseting the slider's hidden property to YES in cellForRowAtIndexPath. I also do this for other views in the cell I need hidden by default. This creates a new issue when I scroll back up to the first cell, the slider is hidden because the property is reset in cellForRowAtIndexPath. This leads me to believe I'm doing something wrong.
Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
SongsCustomCell *songCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (songCell == nil) {
songCell = [[SongsCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
NSLog(#"new cell created");
}
NSDictionary *dictionary = [parseTrackArray objectAtIndex: indexPath.row];
NSString *trackTitle = [dictionary objectForKey:#"trackTitle"];
NSString *trackLink = [dictionary objectForKey:#"trackStreamLink"];
songCell.trackLinkString = trackLink;
songCell.trackTitleString = trackTitle;
[songCell.trackTitleLabel setFont:[UIFont fontWithName:#"Calibri" size:22]];
songCell.trackTitleLabel.text = [NSString stringWithFormat:#"%#", trackTitle];
songCell.playButton.hidden = NO;
songCell.playbackSlider.hidden = YES;
songCell.playerHasLoaded = NO;
songCell.moviePlayer.view.hidden = YES;
return songCell;
}

You need to store the hidden / unhidden status of your slider in your data model somewhere, and then set the slider's visibility appropriately from that in cellForRowAtIndexPath. If only one slider is visible at once, you can store the index path as an ivar in your view controller, if not then you will need to have another key in your dictionary holding an NSNumber bool or something.

Your problem is that your cells are being reused when you are scrolling down. So when your slider is visible for a cell thats is scrolling out of view, that cell will be reused and thus the slider is still visible.
To avoid this implement prepareForReuse in your custom cell to reset your slider as soon as your cell is being reused.
Next to that you still need to store the state of the slider in your model, so you are able to restore that state once that object is coming back in view.
Nevertjeless its a good practice to set the default state of your custom cell in prepareForReuse

Related

UITableViewCell background disappears?

The UITableViewController in my app pulls data from a json data source. I have also created a custom UITableViewCell background using CG. There is a very interesting bug that happens and I have no idea why. I will walk you through what happens and how I recreate it:
Tap to enter table view.
Without scrolling the table at all I immediately tap on an item in view.
After tapping on that item I press the back button to return to the table view.
If I then scroll down the first cell to appear from off screen will not have my custom back ground. It will just be the default for a cell. Then if I continue to scroll down every 10th cell will have the same issue.
This bug only occurs in this exact process. If I were to scroll the table view at all before tapping on an item it would not happen.
Here is the relevant code for the tableview controller:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Will remove all of the used codes from the table if setting is enabled
if (self.shouldHideCodes) {
NSMutableArray *tempArray = [self.jsonCodeData mutableCopy];
[tempArray removeObjectsInArray:[self.usedCodes usedCodes]];
self.jsonCodeData = tempArray;
}
return [self.jsonCodeData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
if (self.jsonCodeData) {
cell = [tableView dequeueReusableCellWithIdentifier:#"code cell"];
if ([cell isKindOfClass:[CodeCellTVC class]]) {
CodeCellTVC *tvcCell = (CodeCellTVC *)cell;
if (![tvcCell.backgroundView isKindOfClass:[CustomCellBackground class]]) {
tvcCell.backgroundView = [[CustomCellBackground alloc] init];
}
NSDictionary *codeDict = [self.jsonCodeData objectAtIndex:indexPath.row];
// Retrieve code string from dictionary
NSString *codeText = [NSString stringWithFormat:#"%#", [codeDict objectForKey:#"code"]];
tvcCell.codeTableLabel.text = codeText;
}
}
return cell;
}
The thing that confuses me is how it reacts. That when the bug happens every 10th cell has the issue and not every one. I don't have anything outside of these method's that deal with the tableviewcell itself.
I understood your problem, you did a wrong at the time of initializing the cell,Every time your intializing the cell, so that every time memory will allocate for that cell, it will create memory issue.
Edit the code like bellow it will work for you.
cell = [tableView dequeueReusableCellWithIdentifier:#"code cell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"code cell"];
}

How to access values entered in a UITableViewCell?

I have a prototype table in my app witch I populate with a customTableViewCell class with a UITextField inside.
In my navigation bar I got a save button.
The question is, how to access this dynamic created cell's to get the UITextField content?
This is my code, you can see that I tried to use NSMutableArray
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"customTableCell";
customTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self.pfCells addObject:cell];
if(cell == nil)
{
cell = [[customTableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
// Configuration
cell.lblName.text = [self.pfFields objectAtIndex: [indexPath row]];
cell.txtType = [self.pfTypes objectAtIndex: [indexPath row]];
if ([[self.pfTypes objectAtIndex:[indexPath row]] isEqualToString: #"n"]) {
[cell.txtField setKeyboardType:UIKeyboardTypeNumberPad];
} else if ([[self.pfTypes objectAtIndex:[indexPath row]] isEqualToString: #"m"]) {
[cell.txtField setKeyboardType:UIKeyboardTypeEmailAddress];
}
return cell;
}
Here's another way to save content from a UITextField contained in a UITableViewCell:
Inside tableView:cellForRowAtIndexPath: set the delegate and a tag for txtField
Implement textFieldDidEndEditing: check for a UITextField tag value an save data in a private variable
Reload UITableView
The biggest advantage of this implementation if the fact that you doesn't need to iterate over whole tableview everytime you change a textfield value.
Quick answer:
#pragma mark - UITextFieldDelegate
- (void)textFieldDidEndEditing:(UITextField *)textField
{
// grab the row we are working on
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
// remove the old key/value pair if it exists and add the new one
[self.modelDictionary removeObjectForKey:indexPath];
[self.modelDictionary setObject:textField.text forKey:indexPath];
}
Be sure to add cell.txtField.delegate = self when configuring your cell. Then in your save button, you'd iterate through the dictionary and save the values -- or just save the dictionary itself.
Also, if you are targeting iOS6 or later, use dequeueReusableCellWithIdentifier:forIndexPath: as this method guarantees a cell is returned and resized properly, so you don't have to check for nil and manually init your cell.
Longer answer:
You generally never want to store your model in your view as you are doing. Aside from it breaking the MVC design patterns, it also causes issues with UITableViews. Specifically, a UITableViewCell will be recycled when it scrolls off the screen. So any values you have in those fields are lost. While you can get away with doing this if you only have visible rows that never scroll off the screen, I would encourage you to avoid this approach altogether.
Instead, you should store the values entered into the textboxes in your model object. The easiest way to do this is to use UITextFieldDelegate's textFieldDidEndEditing: to grab the values after the user enters them, then add these values to your model. You model could be something as simple as an NSDictionary using the indexPath as the key.

Updating Custom Button Image after UITableView being scrolled

I am working on accessing a custom check box which I have already utilized in my project as a custom button, and trying to access it in my table view cell.
Currently, it is prompting the button correctly, and works just fine, except for the case when I scroll my tableview, and then make any further changes on the button (either checkin, or checkout), the button image will overlay the older image and not update the draw.
I am just curious, is there any fix for this?
My code looks something like this:
my code for cellForRowAtIndexPath looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CustomTableCell";
CustomTableCell *cell = (CustomTableCell *)
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"CustomTableCell"
owner:self options:nil];
cell = tableCell;
self.tableCell = nil;
}
CheckBox *chkBox = [[CheckBox alloc] init];
chkBox.frame = CGRectMake(10.0, -10.0, 50.0, 70.0);
[cell.contentView addSubview:chkBox];
[chkBox release];
cell.modelLabel.text =
[[[[[self regData] ShoppingCart] objectForKey:#"Cart"]
valueForKey:#"Model"] objectAtIndex:indexPath.row];
// Configure the cell...
return cell;
}
I haven't implemented the didSelectRowAtIndexPath method yet as that method is suppose to take the viewer to newer screen with more information.
What you're doing is adding the Checkbox every time you display a cell, regardless of whether it's a new or reused cell.
You don't want to do this every time:
CheckBox *chkBox = [[CheckBox alloc] init];
chkBox.frame = CGRectMake(10.0, -10.0, 50.0, 70.0);
[cell.contentView addSubview:chkBox];
[chkBox release];
Only do this if it's a new cell... however by adding the checkbox as a subview and then releasing it, you're losing access to it anyway. To access it you'll need to iterate the subviews of the cell, find the one of type checkbox and then see if it's checked.
It would be better if your CustomTableCell had a Checkbox property that you set, so you could easily access it.

Text in UITableViewCell missing when scroll down

I added several cells to a tableview and each cell has a textField in the right to let users input texts. I find that when I scroll down and go back, the input of the first few lines will disappear. Does anybody know what's the problem?
The following is a piece of my codes:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
*)indexPath
{
//init cell
static NSString *TableIdentifier = #"MyIdentifier";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:TableIdentifier];
if(cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:TableIdentifier] autorelease];
}
//set cell for each row
if([indexPath row] == 0)
{
cell.textLabel.text = #"Row A";
txt_A = [self CreateTextField]; //create textField
[cell.contentView addSubview:txt_A]; //add textField to cell
}
else if([indexPath row] == 1)
{
cell.textLabel.text = #"Row B";
txt_B = [self CreateTextField];
[cell.contentView addSubview:txt_B];
}
else{...} }
You mean the user input will disappear? If so this is probably because cellForRowAtIndexPath: reuses the cell and will execute the [self CreateTextField] again.
So, unless you store the user input before the cell disappears, your cells will be redrawn empty and reinitialized empty.
If you do store the user input, you could reinitialize the cell with the right user input.
I believe that when a cell leaves the screen (due to scrolling) the iPhone immediately releases it to save memory (thus loosing the user input). Now when you scroll back up, it creates a new cell with a new UITextField in the old position with
[self CreateTextField];
You have to store the user input for each text field separately. For example, you could become the text field's delegate and catch
- (BOOL)resignFirstResponder
to be notified when the user leaves the text field focus. At this point you can assume that the user is finished with this particular field and store it.
You can use method to save user inputs:
- (void)textFieldDidEndEditing:(UITextField *)textField
This methods called then user end editing the field. Save user inputs to array, for example.

UITableView Custom Cell Resets Cell Content

Okay I know I'm running up against my limits of understanding as regards objective-c, cocoa, xcode, and blah blah and so on. But here's what I'm trying to do:
I have a tableview in a viewcontroller. The tableview's delegate is the viewcontroller. Viewcontroller has an outlet to the tableview. The table is put together using custom cells (with IB xib) and data from an xml file. In the custom cell there are two buttons - and when the cell is created the button action is added as an addTarget to self (the viewcontroller) which then goes to an action. The viewcontroller button action method gets the row of the button pressed in the table and then changes the cell's text and the button's title.
But of course when I scroll that particular cell out of view and back into view it's been reset to the beginning state. I assume this has to do with cell dequeueing etc. Any ideas?
here's the relevant bits and code:
custom cell: has outlets to buttons and textlabel.
cell create code in the datasource cellForRowAtIndexPath...
{
static NSString *dialogueCellIdentifier = #"dialogueCellIdentifier";
dialogue_cell *cell = (dialogue_cell *)[tableView dequeueReusableCellWithIdentifier:dialogueCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"dialogue_cell" owner:self options:nil];
cell = [nib objectAtIndex:0];
[[cell lButton] addTarget:self action:#selector(lButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
}
[[cell lButton] setTag:[indexPath row]];
NSString *row = [NSString stringWithFormat:#"%i",[indexPath row]];
NSString *en = [[self.dataArray objectForKey:row] valueForKey:#"en"];
cell.mainText.text = en;
return cell;
}
and the lButton method...
NSIndexPath *thisCellPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
dialogue_cell *thisCell = (dialogue_cell *)[self.dialogueTable cellForRowAtIndexPath:thisCellPath];
NSString *row = [NSString stringWithFormat:#"%i",[sender tag]];
if ([thisCell.languageButton.currentTitle isEqualToString:#"en"]) {
[thisCell.languageButton setTitle:#"zc" forState:UIControlStateNormal];
thisCell.mainText.text = [[self.lineArray objectForKey:row] valueForKey:#"lineText_zc"];
} else {
[thisCell.languageButton setTitle:#"en" forState:UIControlStateNormal];
thisCell.mainText.text = [[self.lineArray objectForKey:row] valueForKey:#"lineText_en"];
}
so two questions:
1. is there a way to make the cell retain what is in the label and button name even though it's scrolled offscreen?
2. is there a way to set the label and button name within the cell custom class instead of sending the button action to the viewcontroller that the table is in?
Thanks!
It does not really have anything to do with the dequeueing of cells but the data you are displaying in the cellForRowAtIndexPath. Every time a cell is going to be displayed, this method is called.
I assume you are changing the language of the cell's text on button click. But every time the cell is redrawn you are redisplaying the english text. One way to "retain" the cell's state would be to create an array for the languages being used on each cell and based on the array's value for a particular row, populate the cell's text. You will just have to maintain the state of the cell in the array.
OK i had this problem too, took me awihle but I figure it out, the cell queue is they key here, when you are trying to get your cell back your are using this
NSIndexPath *thisCellPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
dialogue_cell *thisCell = (dialogue_cell *)[self.dialogueTable cellForRowAtIndexPath:thisCellPath];
When the cell is off the view, the table view unloads this cell and instead you get a cell thats in the original state (or prolly nil) when you call this because its n ot longer part of the table view until the cells come back into the view screen.
What you have to do is since you a re enqueuing your cells, I am going to assume you are doing this correctly and you are giving a different name to each cell, instead of doing what you did above you must get your cell like so
cell=[tableView dequeueReusableCellWithIdentifier:dialogueCellIdentifier];
This will give you your cell with the state it was in when the user modified it...Hope this helps!
As for your second question, you can just set the target to be the cell instead of the view controller, this will call that action inside your custom cells class..