开发者

Grouped UITableView rows with varying heights causes content shuffle

开发者 https://www.devze.com 2023-01-27 03:02 出处:网络
Tried my hardest to find the answer here first, but I\'m stuck. I have a UITableView set with UITableViewStyleGrouped with 4 sections, each with one or two rows. In two of the sections I needed a row

Tried my hardest to find the answer here first, but I'm stuck. I have a UITableView set with UITableViewStyleGrouped with 4 sections, each with one or two rows. In two of the sections I needed a row to be a larger height to hold the content I'm sticking in there.

Looks nice except when I scroll up and down, textLablels, accessories and extra subviews start to shift into different rowss and I can't figure out why.

This screenshot shows the table view when it loads and then after I scroll up and down a few times. Each time I scroll different row content shuffles.

I thought I read something about this being an issue with the grouped style. Sure enough, I don't see this issue if I change the table style to default. Am I not allowed to dynamically set the height for some rows when using the grouped style?

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
     if (indexPath.section == kSection_Info && indexPath.row == kSectionRow_InfoPhoto)
     {
      return 84.0;
     }
     else if (indexPath.section == kSection_Level && indexPath.row == kSectionRow_LevelLevel)
     {
      return 70.0;
     }
     return 44.0;
}

I'm setting up each row manually in celForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{   
    static NSString *CellIdentifier = @"RecipientEntryCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];

        switch (indexPath.section)
        {
            case kSection_Info:
            {
                switch (indexPath.row)
                {
                    case kSectionRow_InfoName:
                    {
                        cell.textLabel.text = @"Name";
                        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
                        self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(74, 8, 195, 25)];
                        self.nameLabel.textAlignment = UITextAlignmentRight;
                        self.nameLabel.font = [UIFont systemFontOfSize:16];
                        self.nameLabel.textColor = [UIColor blueColor];
                        self.nameLabel.text = self.currentRecipient.fullName;                       
                        [cell.contentView addSubview:self.nameLabel];

                        break;
                    }
                    case kSectionRow_InfoPhoto:
                    {
                        cell.textLabel.text = @"Photo";
                        self.imageButton = [UIButton buttonWithType:UIButtonTypeCustom];
                        self.imageButton.frame = CGRectMake(10, 14, 64, 64);
                        [self.imageButton addTarget:self action:@selector(onImageButtonTouch:) forControlEvents:UIControlEventTouchUpInside];                   

                        NSString *imageName = @"add_image.png";
                        UIImage *thumb = [UIImage imageNamed:imageName];
                        [self.imageButton setImage:thumb forState:UIControlStateNormal];
                        cell.accessoryView = self.imageButton;

                        break;
                    }
                    default:
                    {
                        break;
                    }
                }
                break;
            }

            case kSection_List:
            {
                switch (indexPath.row)
                {
                    case kSectionRow_ListHasList:
                    {
                        cell.textLabel.text = @"Is Listed";                 
                        self.listSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
                        cell.accessoryView = self.listSwitch;                   

                        break;
                    }
                    case kSectionRow_ListBudget:
                    {
                        cell.textLabel.text = @"List Amount";
                        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
                        self.budgetLabel = [[UILabel alloc] initWithFrame:CGRectMake(124, 8, 145, 25)];
                        self.budgetLabel.textAlignment = UITextAlignmentRight;
                        self.budgetLabel.font = [UIFont systemFontOfSize:16];
                        self.budgetLabel.textColor = [UIColor blueColor];
                        self.budgetLabel.text = [@"$" stringByAppendingFormat:@"%0.2f", [self.currentRecipient.budget floatValue]];                     
                        [cell.contentView addSubview:self.budgetLabel];

                        break;
                    }
                    default:
                    {
                        break;
                    }
                }
                break;
            }   

            case kSection_Level:
        开发者_StackOverflow社区    {
                switch (indexPath.row)
                {               
                    case kSectionRow_LevelLevel:
                    {
                        self.levelSlider = [[UISlider alloc] initWithFrame:CGRectMake(8, 2, 284, 40)];
                        self.levelSlider.minimumValue = 0.0;
                        self.levelSlider.maximumValue = 100.0;
                        self.levelSlider.continuous = YES;

                        UIImage *meterImage = [UIImage imageNamed:@"meter_labels.png"];
                        UIImageView *meterView = [[UIImageView alloc] initWithFrame:CGRectMake(8, 32, 284, 24)];
                        [meterView setImage:meterImage];

                        [cell.contentView addSubview:self.levelSlider];
                        [cell.contentView addSubview:meterView];
                        [meterImage release];

                        break;
                    }
                    case kSectionRow_LevelHasLevel:
                    {
                        cell.textLabel.text = @"Show Level";
                        self.levelSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease];
                        cell.accessoryView = self.levelSwitch;                              
                        break;
                    }                       
                    default:
                    {
                        break;
                    }
                }
                break;
            }

            case kSection_RecipientDelete:
            {
                cell.textLabel.text = @"Delete Recipient";          
                cell.textLabel.textAlignment = UITextAlignmentCenter;
                cell.textLabel.textColor = [UIColor blueColor];             
                break;
            }

            default:
            {
                break;
            }
        }
    }

    return cell;
}


The "content shuffling" you see is most likely due to improper handling of cell re-use.

There is no issue specifically with the grouped style. The problem is more likely to manifest itself in that style because fewer cells fit in the screen which requires more scrolling and requires more cell re-use.

You are setting up the cell contents only when creating cells (when cell == nil). When a cell scrolls off the screen, it goes into the re-use queue. The row at the other end that is now visible re-uses the cell view that is in the re-use queue. The re-used cell contains the contents of some other row.

When all the cells are alike (at least in regard to the UI controls and not the data), this isn't a problem. When all or some of the cells are different, you get controls appearing where you don't expect them.


Because you only have a small number of rows and each one is layed out differently, the quick (and perhaps dirty) solution is to use a different re-use identifier for each cell like this:

NSString *CellIdentifier = 
    [NSString stringWithFormat:@"RecipientEntryCell-%d-%d", 
     indexPath.section, indexPath.row];

This is not at all recommended if the table view is going to have lots of different cells since every cell for every row in the table will be in memory at the same time (instead of just the few on the screen). Do not use a unique identifier as a way to solve any and all cell re-use problems.

The Table View Programming Guide shows an alternate way to design table views like this where you have a few cells with different layouts. See "The Technique for Static Row Content" on this page.


Ultimately, it's better if you understand that the table view re-uses cells for good reasons and not try to workaround it all the time. Generally, the cellForRowAtIndexPath should look like this:

static NSString *CellIdentifier = @"CellIdentifier";

UITableViewCell *cell = [tableView dequeueReusableCell...
if (cell == nil) {
    //Create cell...
    //Set UI content that applies to all rows (if any)...
}
else {
    //Cell is being re-used.
    //Remove UI content that doesn't apply to this row...
}

//Add UI content that applies only to this row...

//Copy values from data source to cell UI controls...

return cell;

If you construct the cells as shown above, do not maintain class-level references to UI controls inside cells (like nameLabel, imageButton, etc). Instead, the control values should be set in cellForRowAtIndexPath from a backing data variable (model) and the value should be read or saved back to the backing data variable as soon as the UI control changes.

For example, instead of storing a reference to a UISlider in a cell, store the current slider value as a float ivar. Initialize the float where appropriate (eg. viewDidLoad). In cellForRowAtIndexPath, create the UISlider, set its value using the float ivar, and have the slider call a method when its value changes. In that method, copy the slider's value to the float ivar.

Hope this helps.

0

精彩评论

暂无评论...
验证码 换一张
取 消