0

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;
    }
}
Peter Warbo
  • 11,136
  • 14
  • 98
  • 193

3 Answers3

1

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.

Nick Lockwood
  • 40,865
  • 11
  • 112
  • 103
0

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 !

Legolas
  • 12,145
  • 12
  • 79
  • 132
  • I rather not subclass UITableViewCell. But I've seen other handle it (I assume without hiccups) http://stackoverflow.com/q/409259/294661 – Peter Warbo Jan 19 '12 at 15:18
  • Subclassing is the only solution to getting rid of the reusability problem !! – Legolas Jan 19 '12 at 18:12
  • @PeterWarbo Sure. That's right ! That will get rid of the reusability problem. But out of experience... I would again go with `subclassing` as that will help you fix issues in the long run ! – Legolas Jan 20 '12 at 16:29
0

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).

viggio24
  • 12,316
  • 5
  • 41
  • 34