简体   繁体   中英

Dynamic height reusable UITableViewCell

This question has been asked 100 times on SO, but I'm not able to solve my problem.

I've got two customizable cells. Both can have dynamic heights. My tableView:heightForRowAtIndexPath: returns the proper height for each cell, but when cells are reused, the separator line between cells isn't in the right spot 100% of the time. I can't use the hack of adding my own line in my cells because sometimes the overlap is so bad you can't click on the right cell.

What do I need to do to make sure it uses the proper height?

Here is an example of the separator line being in the wrong spots (supposed to be right above the title):

屏幕截图

Here is how I set my heights:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    InspectionItem *item = [items objectAtIndex:indexPath.row];
    int t = [item.rating.ratingType ratingTypeK];

    if (t == RatingTypeTextfield){
        CGFloat height = [OQCTextFieldCell heightOfCellWithTitle:item.name
                                                         subtext:item.descriptionText
                                                            text:item.comment
                                                  andScreenWidth:self.view.frame.size.width];
        return height;
    }
    else {
        CGFloat height = [OQCButtonCell heightOfCellWithTitle:item.name
                                                      subtext:item.descriptionText
                                               andScreenWidth:self.view.frame.size.width];
        return height;
    }
}

Again, these class methods return the proper heights.

Here is how I set my cells up (short version):

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    InspectionItem *item = [items objectAtIndex:indexPath.row];
    int t = [item.rating.ratingType ratingTypeK];

    if (t == RatingTypeTextfield){
        static NSString *CellIdentifier = @"TextFieldCell";
        OQCTextFieldCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

        cell.titleLabel.text = item.name;
        ...
        return cell;
    }
    else {
        static NSString *CellIdentifier = @"ButtonCell";
        OQCButtonCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

        cell.titleLabel.text = item.name;
        ...
        return cell;
    }
}

Here is how I'm registering my cells:

UINib *buttonNib = [UINib nibWithNibName:@"OQCButtonCell" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:buttonNib forCellReuseIdentifier:@"ButtonCell"];

UINib *textFieldNib = [UINib nibWithNibName:@"OQCTextFieldCell" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:textFieldNib forCellReuseIdentifier:@"TextFieldCell"];

UINib *flagNib = [UINib nibWithNibName:@"OQCFlagCell" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:flagNib forCellReuseIdentifier:@"FlagCell"];

My cells only overwrite layoutSubviews and they do call super first thing.

I need the solution to be good for iOS 5.2+.

Update

Here is my code for layoutSubviews . I do believe that my custom method heightOfCellWithTitle:subtext:text: returns the right height since it matches what the layoutSubview height variable is at the end of this method.

- (void)layoutSubviews
{
    [super layoutSubviews];

    CGFloat height = 0;

    // Title + Header background view
    NSString *title = titleLabel.text;
    CGRect frame = self.titleLabel.frame;
    CGSize size = [title sizeWithFont:titleLabel.font
                    constrainedToSize:CGSizeMake(frame.size.width, 9999)
                        lineBreakMode:titleLabel.lineBreakMode];
    size.width = frame.size.width; // don't let it shrink
    titleLabel.numberOfLines = size.height / titleLabel.font.lineHeight;
    frame.size = size;
    self.titleLabel.frame = frame;

    frame = self.headerBackgroundView.frame;
    frame.size.height = size.height + 8;
    self.headerBackgroundView.frame = frame;

    // single line of text should be 30
    height += frame.size.height;

    CGRect subContentFrame = subContentView.frame;
    subContentFrame.origin.y = height;

    CGFloat subHeight = 0;

    // Label
    title = subtextLabel.text;
    if ([title length] > 0){
        subHeight += 5; // top margin
        size = [title sizeWithFont:subtextLabel.font
                 constrainedToSize:CGSizeMake(subtextLabel.frame.size.width, 9999) lineBreakMode:subtextLabel.lineBreakMode];
        size.width = self.contentView.frame.size.width - 52; // don't let it shrink
        subtextLabel.numberOfLines = size.height / subtextLabel.font.lineHeight;
        frame = self.subtextLabel.frame;
        frame.size = size;
        frame.origin.y = subHeight;
        self.subtextLabel.frame = frame;
        // subtext height + bottom margin
        subHeight += size.height + 5;
    }
    else {
        subHeight += 25;
    }
    // Button
    frame = button.frame;
    frame.origin.y = subHeight;
    button.frame = frame;

    subHeight += frame.size.height + 25;

    subContentFrame.size.height = subHeight;
    subContentView.frame = subContentFrame;

    // reposition detailIndicator
    frame = self.detailIndicator.frame;
    frame.origin.x = subContentView.frame.size.width - 37;
    self.detailIndicator.frame = frame;

    height += subHeight;

    // Drawer
    frame = CGRectMake(0, 0, self.frame.size.width, height);

    [self.drawerView setFrame:frame];

    self.backgroundView = self.drawerView;
}

Also, quick note about the numbers I've been checking.

I have a log on layoutSubviews and tableView:heightForRowAtIndexPath: which give me correct heights (I'm basing that off of layoutSubviews since it is recalculating the height for the entire cell). And then I have a log for tableView:cellForRowAtIndexPath: which at the end just before I return the cell I print out the cell's height, and it is an old height of a reused view, which I kind of expect. I would assume that after the cell is returned that iOS will recalculate what the position of the cell should be based on tableView:heightForRowAtIndexPath: and reposition the views with layoutSubviews .

so if you layout the cells dynamically in the cell class and overwrite layoutSubviews this should be enough in heightForRowAtIndexPath:

UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
float height = cell.frame.size.height;
return height;

remember that you have to "reset" all uiviews in your cell to the start values. otherwise the the cell will use the current values, ie if you have a label at y=10 and set it programmatically to y=20, next time the cell is used the cell still have y=20.

Have you tried calling

[cell setNeedsLayout];
[cell layoutIfNeeded];

before returning it?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM