Reuse cell... :( - objective-c

I have a table with 9 sections and 56 rows.
I want to add a text label for each cell. I created a NSArray menuList with 56 NSDictionaries and an array containing the number of rows in each section (sectionsArray).
Here is my code, but it doesn't work properly at all:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//Use existing cell (reusable)
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
//If no existing cell, create a new one
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier] autorelease];
//Define cell accessory type
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
//Create a subView for the cell
CGRect subViewFrame = cell.contentView.frame;
subViewFrame.origin.x += kInset;
subViewFrame.size.width = kInset + kSelectLabelWidth;
UILabel *selectedLabel = [[UILabel alloc] initWithFrame:subViewFrame];
//SubView design
selectedLabel.textColor = [UIColor blackColor];
selectedLabel.highlightedTextColor = [UIColor whiteColor];
selectedLabel.backgroundColor = [UIColor clearColor];
[cell.contentView addSubview:selectedLabel];
int indRow = 0;
for (int i =0; i < indexPath.section; i++) {
indRow += [[sectionsArray objectAtIndex:i] intValue];
}
indRow += indexPath.row;
NSDictionary *cellText = [menuList objectAtIndex:indRow];
selectedLabel.text = [cellText objectForKey:#"selection"];
[selectedLabel release];
}
return cell;
}
What's wrong in this code?
In iOSSimulator, i see that cell's text change sometimes when I'm scrolling, and labels are not in the right order.

All the code that fills in your cells is with in the:
if (cell == nil) {
statement, so you end up creating a small number of cells and filling them in (when cell==nil) and then just return the dequeued ones.
This is what it should be:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//Use existing cell (reusable)
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
//If no existing cell, create a new one
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier] autorelease];
//Define cell accessory type
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
//Create a subView for the cell
CGRect subViewFrame = cell.contentView.frame;
subViewFrame.origin.x += kInset;
subViewFrame.size.width = kInset + kSelectLabelWidth;
UILabel *selectedLabel = [[UILabel alloc] initWithFrame:subViewFrame];
//SubView design
selectedLabel.textColor = [UIColor blackColor];
selectedLabel.highlightedTextColor = [UIColor whiteColor];
selectedLabel.backgroundColor = [UIColor clearColor];
[cell.contentView addSubview:selectedLabel];
}
// At this point cell whether it is a reused cell or a new one
// cell points to the object we need to fill in.
int indRow = 0;
for (int i =0; i < indexPath.section; i++) {
indRow += [[sectionsArray objectAtIndex:i] intValue];
}
indRow += indexPath.row;
NSDictionary *cellText = [menuList objectAtIndex:indRow];
selectedLabel.text = [cellText objectForKey:#"selection"];
[selectedLabel release];
return cell;
}
This is what the code is doing:
Try to get a reusable cell
If no reusable cell is available
{
create a new cell
}
Fill in the values for the cell
So when a cell scrolls of the screen it is put into a reuse queue. When a cell is about to come onto the screen your cellForRowAtIndexPath is called. Allocating a new cell is slower than reusing an existing one, so you first try to get a cell from the reuse queue, but if one is not available you create a new one. Then you fill in your values.

Related

tableviews cells are changing after scrolling down

I am making a form within a grouped tableview. In this form I have UIswitches and textfields. But after scrolling down, the cells styles are changing.
Here is my cellForRowAtIndex
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
static NSString *MyIdentifier = #"GenericCell";
cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
}
NSString *text = nil;
if(indexPath.section == CREDENTIALS_SECTION){
if (indexPath.row == 0) {
NSLog(#"tot hier login");
UITextField *login = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
login.adjustsFontSizeToFitWidth = YES;
login.placeholder = #"example#gmail.com";
login.keyboardType = UIKeyboardTypeEmailAddress;
login.returnKeyType = UIReturnKeyNext;
login.backgroundColor = [UIColor clearColor];
login.tag = 0;
login.delegate = self;
[login setEnabled: YES];
[cell addSubview:login];
}else if (indexPath.row == 1){
NSLog(#"tot hier pass");
UITextField *pass = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
pass.adjustsFontSizeToFitWidth = YES;
pass.placeholder = #"Required";
pass.keyboardType = UIKeyboardTypeDefault;
pass.returnKeyType = UIReturnKeyDone;
pass.secureTextEntry = YES;
pass.backgroundColor = [UIColor clearColor];
pass.tag = 0;
pass.delegate = self;
[cell addSubview:pass];
}
if (indexPath.row == 0) { // Email
text = #"Email";
}
else if(indexPath.row == 1) {
text = #"Password";
}
}else if(indexPath.section == METHODS_SECTION){
UISwitch *toggleSwitch = [[UISwitch alloc]initWithFrame:CGRectMake(220, 10, 100, 30)];
toggleSwitch.tag = indexPath.row;
[toggleSwitch addTarget:self action:#selector(toggleSwitched:) forControlEvents:UIControlEventValueChanged];
[cell addSubview:toggleSwitch];
if (indexPath.row == 0) { // Web
text = #"Web applicatie";
}
else if(indexPath.row == 1) { //Mobile
text = #"Mobiele applicatie";
}
else if(indexPath.row == 2) { //Mail
text = #"E-mail";
}
}else if(indexPath.section == PHONE_SECTION){
UITextField *phoneText = [[UITextField alloc] initWithFrame:CGRectMake(20, 10, 185, 30)];
phoneText.adjustsFontSizeToFitWidth = YES;
phoneText.font = [UIFont fontWithName:#"Arial-BoldMT" size:18];
phoneText.keyboardType = UIKeyboardTypeNumberPad;
phoneText.delegate = self;
phoneText.textColor = [UIColor blackColor];
phoneText.text = _person.phone;
[cell addSubview:phoneText];
}else if(indexPath.section == REMARK_SECTION){
UITextView *textView = [[UITextView alloc]initWithFrame:CGRectMake(20, 10, 280, 260)];
textView.text = _person.remark;
textView.delegate = self;
textView.font = [UIFont fontWithName:#"Arial" size:15.0];
textView.backgroundColor = [UIColor clearColor];
[cell addSubview:textView];
text = #"";
}else if(indexPath.section == BUTTON_SECTION){
cell.backgroundColor = [UIColor redColor];
text = #"test";
}
cell.textLabel.text = text;
return cell;
}
After some searching I found that more people are having this problem. And that the problem lays in this piece of code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
static NSString *MyIdentifier = #"GenericCell";
cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
}
NSString *text = nil;
But I don't find a solution for it.
Hope anybody can help!
Kind regards!
Clarification
Oké so here you see a screenshot of my form. below I have a red cell (save button) when I scroll down other cells are getting a red background. And some cells, text property's are changing.
That is not gong to work. Aparently you did not yet fully understand how the re-use mechanism works.
What do you do?
First you fetch a cell to be re-used. If you get one -fine so far but the problem comes later. If you don't get one then you create a new one.
When you have created a new one, which is the case at start before the user begins scrolling, then you add some UIItems depending on section and row. I will explain why this is not actually a smart thing to do.
Then the user scrolls. Cells will dissappear from screen and then made available for re-use. Then you will fetch the cells for re-use. But it may well happen that those cells already have additional UI-Items on them because you have used them before in that way. In the following process you will add new UI Items regardless whether there are already additional UI-Items on that very cell.
What can you do:
Create your own custom table cell subclasses. One subclass for each set of additional ui items that you may need. That is probably the neatest way of doing it. For each subclass use a different re-use identifier (!!!)
This is what I would recommend!
However, there are alternatives:
You could still live with your concept but invent an individual type of re-use identfier for each type of cell that has some type of additional ui item on it. If so, then make sure that these UI items are only created and added as sub-views in the if (cell == nil) branch of your code. Only create them once and then re-use them. Cell reuse-IDs could be "email-display", "email-input" , "password-display", "password-input", "switch", ...
A variance of the solution above would be, to calculate row and section
into the reuse-identifier. Such as "cell-id-0.2" for section 0 and
row 2 - or so. But still you will have to make sure that you really
re-use the additional UI views and do not re-create them every time
when the cell is filled with data. Plus, the layout in your first section varies depending on whether you want to input password and e-mail or just display them. You will still have to deal with those variations.
If cell == nil - meaning if a cell is re-used - then first clean it from every UI item that you may have added before. You can do that by tagging your UIViews with - let's say 99 - (anything different from 0 should do) upon creation and when reusing enumerate over all subviews and remove those, which have the tag 99. Despite that you can stick with the code that you have already made.
The easiest fix is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"GenericCell"] ;
//some more code
return cell;
}
This would remove the reusability from the tableview, but since it's a limited settings view, it can be ok. I would still advice taking 1 or 2 from Hermann Klecker's solutions.
If you also need to persist UIControl state then use
static NSString *MyIdentifier = [NSString stringWithFormat:#"GenericCell%d",indexPath.row];
It will always return your unique table row and you can use it as required.
Try to remove all subviews from cell before reusing it. Try the code :
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
}
else
{
[cell.contentView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
}
Remove all subviews before adding the subviews on cell.
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SimpleTableIdentifier]autorelease];
}
else
{
//To remove the subview of cell.
for (UIView *vwSubviews in [cell.contentView subviews])
{
[vwSubviews removeFromSuperview];
}
}
It may solves your problem.
Actually you have some bad code here.
In the mehthod
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Unless it is not in if (cell == nil), you should NOT initialize and use any
-(void)addSubview:(UIView*)view
Why?
The cells are views which are reused from tableview. So If you add some subview, next time while reusing the cell, it will be added more subviews on it. Simply they are overlapped and may cause MEMORY LEAK.
Do not forget that cells are reusable. So;
if I have the following code unless I do not set text somewhere else. It is expected to all cells has the text in their text labels "this is a text". Because they are reusable.
if (someChangingBool) {
cell.textLabel.text = #"this is a text";
}
So I need to have an else for that if which sets the text something else.
For more Information.

UITableView - When scrolling too fast, contents of last cell replaces first cell

This is probably caused by poor design, but when I scroll my table too fast, and then scroll back to the top, the view that is placed in the last table cell is also placed over the first table cell in the tableView.
I think it is probably caused by my use of if statements to put some static content in the first section and dynamic content in the second section.
Any help is appreciated.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
if (indexPath.section == 0) {
if (indexPath.row == 0) {
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UITextField *textField = [[UITextField alloc]initWithFrame:CGRectMake(10, 13, 282, 20)];
textField.clearsOnBeginEditing = NO;
textField.placeholder = #"enter template name";
textField.font = [UIFont systemFontOfSize:15.0];
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
textField.delegate = self;
textField.text = [selectedTemplate name];
[textField addTarget:self action:#selector(nameDidChange:) forControlEvents:UIControlEventEditingChanged];
[cell.contentView addSubview:textField];
} else {
cell.textLabel.text = #"Add a new question";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
} else {
NSString *label = [[sortedQuestions valueForKey:#"questionText"] objectAtIndex:indexPath.row];
CGSize stringSize = [label sizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:CGSizeMake(230, 9999) lineBreakMode:NSLineBreakByWordWrapping];
UITextView *textV=[[UITextView alloc] initWithFrame:CGRectMake(5, 5, 230, stringSize.height+10)];
textV.font = [UIFont systemFontOfSize:15.0];
textV.text=label;
textV.textColor=[UIColor blackColor];
textV.editable = NO;
textV.userInteractionEnabled = NO;
textV.backgroundColor = [UIColor colorWithRed:0.97f green:0.97f blue:0.97f alpha:1.00f];
[cell.contentView addSubview:textV];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
Here is what I ended up doing based on Wienke's recommendation. I added 3 prototype cells to my storyboard named Cell,Cell2,Cell3.
Relevant code:
static NSString *CellIdentifier = #"Cell";
static NSString *CellIdentifier2 = #"Cell2";
static NSString *CellIdentifier3 = #"Cell3";
UITableViewCell *cell;
if (indexPath.section == 0 && indexPath.row == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
} else if (indexPath.section == 0 && indexPath.row == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2 forIndexPath:indexPath];
} else if (indexPath.section == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier3 forIndexPath:indexPath];
}
I also added this to check my dynamic cells and remove any lingering subviews that could be hanging around before adding the new subview.
if ([cell.contentView subviews]){
for (UIView *subview in [cell.contentView subviews]) {
[subview removeFromSuperview];
}
}
I know this already has an accepted answer, but I figured I would go slightly deeper into the "why" this was happening, and the solution.
In layman's terms, the UITableView refreshing was lagging with the presentation speed asked. In other words, due to the rapid scroll, the table view was expected to repopulate the content faster than it could sort through the expected cell to reuse for it's respective index (row).
The simplest solution, which Wienke touched on, is to create different cell identifiers for the first, let's say three (3) cells. This will allow the table view to be able to clearly differentiate between the 3 cells, preventing any cell misplacement.
Perhaps the best approach here would be to assign relevant cell identifiers to each cell (depending on the context and the number of cells). This way the table view knows precisely which cell (with it's respective ID) goes where. Something as simple as the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellID = [NSString stringWithFormat:#"cell%lu", indexPath.row];
__kindof UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (!cell) {
// Create your cell here.
cell = [...allocate a new cell...] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
return cell;
}
It looks like a cell that was layed out for, say, section 0 row 0 is being dequeued and used for, say, section 0 row 1, which does not replace all of the settings that were made for it when it was holding section 0 row 0 material.
You might want to consider using 3 separate cell identifiers, one for section 0 row 0, one for section 0 row 1, and one for all the rest. You'll need a preliminary set of if statements (or switch-case statements) so that you use the right identifier when calling dequeueReusableCellWithIdentifier:.

UITableView duplicate row's

This is the code im using in the 'cellForRowAtIndexPath'
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellIdentifier = #"OBBirthControlMethodsTableCell";
OBCustomDetailCell *cell = (OBCustomDetailCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSLog(#"cell id - %#",cell.subviews);
CGRect frame = [tableView rectForRowAtIndexPath:0];
if(nil == cell)
{
cell = [[[OBCustomDetailCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
if (indexPath.row != 3)
{
//Setting the basic template
UIView *template = [[UIView alloc] init];
template.tag = indexPath.row+10;
NSLog(#"path index = %d",indexPath.row);
UIImageView *templateImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0,
200,
frame.size.height)];
UILabel *templateLabel = [[UILabel alloc] initWithFrame:CGRectMake(templateImage.frame.size.width+20,
0,
cell.frame.size.width - templateImage.frame.size.width+20,
frame.size.height)];
[template addSubview:templateImage];
[template addSubview:templateLabel];
[cell.contentView addSubview:template];
}
}
UIView *templateView = [cell.contentView viewWithTag:indexPath.row + 10];
if (templateView)
{
NSLog(#"Gotten a templateView object");
if (indexPath.row == 0)
{
templateView.frame = frame;
for (UIView *view in templateView.subviews)
{
if ([view isKindOfClass:[UIImageView class]])
{
[(UIImageView *)view setImage:[UIImage imageNamed:#"baby.jpeg"]];
}
else if ([view isKindOfClass:[UILabel class]])
{
[(UILabel *)view setText:#"This is not working"];
}
}
}
else
{
templateView.frame = CGRectMake(0, 50,
frame.size.width,
frame.size.height);
}
}
return cell;
}
But the issue is that the new cell is giving me the same values os the old cell the new cell that is dequeued once you scroll down ..
EDIT
A duplicate cell is being created as soon as we scroll down to a new cell with the same vales of the 1st cell ..
I would like the UIView to be created for only select rows() ..if (indexPath.row != 3)
and i would like the location of the UIView to be different in some of the rows .. if (indexPath.row == 0)
There are a couple of problems with this bit of code. First and foremost, the primary cause of your issues is this bit:
template.tag = indexPath.row+10;
Why are you doing this? Just use a constant value, like 10. No need to involve the index path, since that will change for each cell. This will cause viewWithTag: to fail for reused cells, and it will return nil.
Second, you can't only set up your template cell for indexPath.row != 3, because at some point, the non-template cell may be reused. It will not have the template views, so the following layout will fail. You'll need to use two reuse identifiers for the two types of cells. The final product should look something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *templateCellIdentifier = #"OBBirthControlMethodsTableCell";
static NSString *otherCellIdentifier = #"OtherTableCell";
if (indexPath.row != 3) {
// Handle normal cells
OBCustomDetailCell *cell = (OBCustomDetailCell *)[tableView dequeueReusableCellWithIdentifier:templateCellIdentifier];
if (cell == nil) {
cell = [[[OBCustomDetailCell alloc] initWithStyle:UITableViewStyleDefault reuseIdentifier:templateCellIdentifier] autorelease];
// Set up template cell
}
// Handle per-cell data
} else {
// Handle special cells
OBCustomDetailCell *cell = (OBCustomDetailCell *)[tableView dequeueReusableCellWithIdentifier:otherCellIdentifier];
if (cell == nil) {
cell = [[[OBCustomDetailCell alloc] initWithStyle:UITableViewStyleDefault reuseIdentifier:otherCellIdentifier] autorelease];
// Set up other cell
}
// Handle per-cell data (not really necessary if there's only one of these)
}
}

IOS custom cell with labels showing wrong text when cell reused

I have been trying to figure this out for a bit. I create a custom cell in its own xib file. In my view controller I have setup a table view controller with sections. The data that is being pulled into the table View is based off a fetch request controller from some core data that I have. I set up the custom cell in the cellForRowAtIndexPath function. I am creating a label for each cell within this function and populating the label with some data from the managed object. Everything seems ok when I first run. However, when I try to scroll up and down and new cells are reused the data in the labels are placed in the wrong cells. I have seen and heard this has to do with the reuse of cells. However, have not seen much examples on correcting this issue. Below is some of the code I have in my cellForRowAtIndexPath function. Let me know if any other input may be needed. Thanks for any help.
-(UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:#"CustomCell"];
/* do this to get unique value per cell due to sections. */
NSInteger indexForCell = indexPath.section * 1000 + indexPath.row + 1;
NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *lastSession = nil;
UILabel *lastSessionLabel = nil;
if(cell == nil) {
lastSession = [managedObject valueForKey:#"last_session"];
[self.tableView registerNib:[UINib nibWithNibName:#"CustomCell"
bundle:[NSBundle mainBundle]]
forCellReuseIdentifier:#"CustomCell"];
self.tableView.backgroundColor = [UIColor clearColor];
cell = [aTableView dequeueReusableCellWithIdentifier:#"CustomCell"];
lastSessionLabel = [[UILabel alloc]initWithFrame:CGRectMake(410,55, 89, 35)];
lastSessionLabel.textAlignment = UITextAlignmentLeft;
lastSessionLabel.tag = indexForCell;
lastSessionLabel.font = [UIFont systemFontOfSize:17];
lastSessionLabel.highlighted = NO;
lastSessionLabel.backgroundColor = [UIColor clearColor];
cell.contentView.tag = indexForCell;
[cell.contentView addSubview:lastSessionLabel];
} else {
lastSessionLabel = (UILabel *)[cell viewWithTag:indexForCell];
}
if (lastSession && lastSession.length) {
lastSessionLabel.text = lastSession;
}
cell.textLabel.text = [NSString stringWithFormat:#"%#%#%#%#", #"Dr. ",
[managedObject valueForKey:#"first_name"],
#" " ,
[managedObject valueForKey:#"last_name"]];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.editingAccessoryType = UITableViewCellAccessoryNone;
return cell;
}
** Revised Code **
Below are the changes to code: in viewDidLoad is the following:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"CustomCell"
bundle:[NSBundle mainBundle]]
forCellReuseIdentifier:#"CustomCell"];
self.tableView.backgroundColor = [UIColor clearColor];
}
in -(UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:#"CustomCell"];
NSInteger indexForCell = indexPath.section * 1000 + indexPath.row + 1;
NSLog(#"index for cell: %d",indexForCell);
NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *lastSession = [managedObject valueForKey:#"last_session"];
UILabel *lastSessionLabel = nil;
if(cell == nil) {
NSLog(#"Cell is nil! %#", [managedObject valueForKey:#"first_name"]);
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CustomCell"];
self.tableView.backgroundColor = [UIColor clearColor];
}
lastSessionLabel = [[UILabel alloc]initWithFrame:CGRectMake(410,55, 89, 35)];
lastSessionLabel.textAlignment = UITextAlignmentLeft;
lastSessionLabel.tag = indexForCell;
lastSessionLabel.font = [UIFont systemFontOfSize:17];
lastSessionLabel.highlighted = NO;
lastSessionLabel.backgroundColor = [UIColor clearColor];
[cell.contentView addSubview:lastSessionLabel];
/* Appropriate verbiage for nil last session. */
if (lastSession && lastSession.length) {
lastSessionLabel.text = lastSession;
}
return cell;
}
I am still having issues again with the label cell text changing when I scroll for different cells. I read some where about maybe having to use the prepareForReuse function for this.
You are only fetching lastSession when you create a new cell. Try putting this line before the if(cell == nil) statement.
lastSession = [managedObject valueForKey:#"last_session"];
I.e. this:
NSString *lastSession = [managedObject valueForKey:#"last_session"];
in stead of this:
NSString *lastSession = nil;
UPDATE
You are also setting the same tag for two views:
lastSessionLabel.tag = indexForCell;
...
cell.contentView.tag = indexForCell;
Based on your code sample you should only use the first line, i.e. set the tag for the lastSessionLabel
SECOND UPDATE
You should also only call registerNib: once in your view lifecycle, e.g. in viewDidLoad, not every time you need a new cell. Furthermore, you should create a new cell if cell == nil in stead of using dequeueReusableCellWithIdentifier:. E.g.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CustomCell"];

Cocoa-Touch – UITableView dequeueReusableCellWithIdentifier gives me wrong cell data

I'm trying to use a UITextField inside a UITableViewCell as you can see in the code below. It seems that when the tableview goes off screen some data that are supposed to be in the cells are mixed up. I would think that there is some problem going on with the method [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier]; not being able to give me a "proper" cell after the tableview has gone off screen. What is the reason for this?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *addGroupContactCellIdentifier = #"AddGroupContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:addGroupContactCellIdentifier];
if ([indexPath section] == 0) { // Group Name Section
cell.textLabel.text = #"Name";
UITextField *groupNameTextField = [[UITextField alloc]initWithFrame:CGRectMake(80, 10, 210, 22)];
groupNameTextField.textAlignment = UITextAlignmentLeft;
groupNameTextField.backgroundColor = [UIColor clearColor];
groupNameTextField.placeholder = #"Type Group Name";
//groupNameTextField.borderStyle = UITextBorderStyleLine;
groupNameTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
groupNameTextField.returnKeyType = UIReturnKeyDone;
groupNameTextField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
groupNameTextField.delegate = self;
[cell.contentView addSubview:groupNameTextField];
}
}
if ([indexPath section] == 1) { // Contacts Section
cell.textLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"name"];
cell.detailTextLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"number"];
}
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
UPDATE:
So I subclassed UITableViewCell but still it exhibits the same error as before. This is now my code for tableView:cellForRowAtIndexPath::
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *addGroupContactCellIdentifier = #"AddGroupContactCell";
if ([indexPath section] == 0) {
UITableViewCellWithUITextField *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
//cell = [[UITableViewCellWithUITextField alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:addGroupContactCellIdentifier];
cell = [[UITableViewCellWithUITextField alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:addGroupContactCellIdentifier textFieldPlaceholder:#"Type Group Name" textFieldDelegate:self];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = #"Name";
// Need to set the UITableViewCell's textLabel properties otherwise they will cover the UITextField
cell.textLabel.opaque = NO;
cell.textLabel.backgroundColor = [UIColor clearColor];
return cell;
} else {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:addGroupContactCellIdentifier];
}
cell.textLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"name"];
cell.detailTextLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"number"];
return cell;
}
}
Third EDIT (I have now 2 different reuseIdentifiers which seem to give me my wanted results):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath section] == 0) { // Group Name Section
static NSString *groupNameCellIdentifier = #"GroupNameCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:groupNameCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:groupNameCellIdentifier];
cell.textLabel.text = #"Name";
UITextField *groupNameTextField = [[UITextField alloc]initWithFrame:CGRectMake(80, 10, 210, 22)];
groupNameTextField.textAlignment = UITextAlignmentLeft;
groupNameTextField.backgroundColor = [UIColor clearColor];
groupNameTextField.placeholder = #"Type Group Name";
//groupNameTextField.borderStyle = UITextBorderStyleLine;
groupNameTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
groupNameTextField.returnKeyType = UIReturnKeyDone;
groupNameTextField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
groupNameTextField.delegate = self;
[cell.contentView addSubview:groupNameTextField];
}
// Customization
return cell;
} else {
static NSString *addGroupContactCellIdentifier = #"AddGroupContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:addGroupContactCellIdentifier];
}
// Customization
cell.textLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"name"];
cell.detailTextLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"number"];
return cell;
}
}
Subclassing is not necessary as some have suggested.
But you cannot use logic like "if ([indexPath section] == 0) {" inside of the "if (cell == nil) {" because that is only called the first time the cell is created, and it will be-re used at other indexes on subsequent recycles.
Instead, you need to use two different CellIdentifiers, so that cells you have set up for section zero do not get re-used at other places in the table. Put your if ([indexPath section] == 0) { before you dequeue the cell and use a different cell identifiers for section zero and subsequent section cells.
Also, make sure you do any indexpath-specific outside of the "if (cell == nil) {" so that it will be applied each time the cell is re-used not just the first time it is created.
You are right ! The problem is definitely due to the reusability feature of UITableView. Apple has done it in such a was so that you can reuse cells, and it works beautifully at a performance stand-point ! And so, when you try scrolling up and down, and indexPath values continue to be the same and your tableView gets data from the cellForRowAtIndexPath that you had defined in your class !
Solution:
You will need to subclass your UITableViewCell and add a UITextField in your -(void)layoutSubviews method.
Then you will need to reference this CustomUITableViewCell and use that to load your TableView.
A link that will help : Read this !
The values are mixed up because when you go offscreen and then reload the table again the cells are dequed from the internal table cells pool but they are not reloaded in the same order they were in the table previously. Note that this mixing will happen even if you have a table with many rows and you scroll it. The solution is to store your textfield data in a "data source" array and then configure the cell.
EXPLANATION
Basically in your code there is one main conceptual flaw: once you have regenerated the cell, you don't configure the content properly (you don't configure it at all). What I mean is that initially, when the table is displayed the first time, the pool is empty. So each new cell that needs to be displayed is recreated from scratch (not dequed from the pool); let's say your table can show 10 cells on screen, so the first 10 cells will be all created from scratch with empty text fields.
Then you start entering text in these fields, and all works correctly.
At a certain point you start scrolling the cell: what happens is that all cells that are in the top will disappear from screen and stored (queued) in the table pool, with their textfield and its edited content; let's say you queue cell at row 0. When a new cell needs to be displayed on bottom of the screen the first thing your code does is to try to deque a cell. Now this time you have a cell in the pool (the cell that was at row 0), this cell is retrieved from the pool and placed in the table, INCLUDED THE TEXTFIELD CONTENT, at row 11. So "magically" you will find a text edited at row 0 in another row, 11. Besides the cells are retrieved in a sparse order from the pool, so after many textfield editings and scrollings you will have a complete mixup.
Solution, and this is the reason of the bug in your code: as soon as the cell has been created or dequed, configure it, that is set the textfield content. How to retrieve the textfield content? store in an array. This is why your view controller is a "data source", because you source data to fill the table. Storing data in the table is a mistake, due to this dequeing mechanism. Example:
groupNameTextField.text=[myTextFieldContentArray objectAtIndex:indexPath.row];
Another solution, but I don't suggest it, is to assign a unique identifier to each cell, that is:
NSString *myCellId = [NSString stringWithFormat:#"CellID_%d_%d",indexPath.section,indexPath.row];
In this case all cells will be enqued with a different name and you will never mix up them.
This solution is working most of the time but it is discouraged for two reasons:
a. it is non-optimal, as you don't reuse cells and so this takes extra memory for similar cells
b. you're not guaranteed that each cell is effectively queued, all in all this logic is inside the table and it's not exposed to the developer, so it may happen that you need to re-generate each time the cell when needed (performance loss).