2
votes

I have an app that displays a TableView with two relatively static cells at the top, followed by a series of custom cells that contain a label and a segmented control. These cells need to vary in height based on the amount of text in the label.

I'm calculating the required cell height in cellForRowAtIndexPath, storing the value in an array, and then using the values from that array in heightForRowAtIndexPath. It appears, however, that heightForRowAtIndexPath is getting called first, so all of my row heights are 0/nil.

How can I specify the row heights based on the particular content of the cell, when the cell height needs to be known before the cell is configured?

Snippet from cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger currentIndex = indexPath.item;
    if (indexPath.item == 0){
        static NSString *CellIdentifier = @"MeasurementCell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
        [self.cellHeightList insertObject:[NSNumber numberWithInt:44] atIndex:currentIndex];
        return cell;
    } else if (indexPath.item == 1){
        if (self.dataController.isScoreAvailable){
            static NSString *CellIdentifier = @"ScoreCell";
            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
            [self.cellHeightList insertObject:[NSNumber numberWithInt:46] atIndex:currentIndex];
            return cell;
        } else {
            static NSString *CellIdentifier = @"ScoreCell";
            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
            cell.accessoryType = UITableViewCellAccessoryNone;
            [self.cellHeightList insertObject:[NSNumber numberWithInt:0] atIndex:currentIndex];
            return cell;
        }
    } else if (indexPath.item > 1){
        NSInteger labelWidth = [UIScreen mainScreen].applicationFrame.size.width - 140; //80 for segment + 3*20 for margins & spacing
        CGSize maxSize = CGSizeMake(labelWidth, MAXFLOAT);
        CGSize labelSize;

        static NSString *CellIdentifier = @"QuestionCell";
        InterviewQuestionCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
        InterviewQuestion *questionAtIndex = [self.dataController objectInListAtIndex:(indexPath.item-2)];
        cell.questionLabel.text = questionAtIndex.questionText;
        labelSize = [cell.questionLabel.text sizeWithFont:[UIFont systemFontOfSize:12.0] constrainedToSize:maxSize lineBreakMode:NSLineBreakByWordWrapping];
        CGRect labelFrame = CGRectMake(0, 0, labelWidth, labelSize.height);
        cell.questionLabel.frame = labelFrame;
        cell.questionLabel.numberOfLines = 0;
        cell.answerControl.selectedSegmentIndex = questionAtIndex.answer;
        cell.answerControl.tag = indexPath.item;
        [self.cellHeightList insertObject:[NSNumber numberWithInt:labelSize.height] atIndex:currentIndex];
        return cell;
    }
    return nil;
}

Code from heightForRowAtIndexPath:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger currentIndex = indexPath.item;
    return [[self.cellHeightList objectAtIndex:currentIndex] integerValue];
}

Thank You!

3

3 Answers

2
votes

First make sure you do not use 'item' on the indexPath, but use 'row'. Item is used I think by UICollectionView, not by UITableView. Second, you need someway to compute the size of a cell without having the cell at hand. Your tableView has a certain width, so based on that you might be able to compute the necessary height. Place this computation in heightForRowAtIndexPath: (if this is intensive, cache it).

Specifically, place the following code not in cellForRowAtIndexPath but in heightForRowAtIndexPath: :

NSInteger labelWidth = [UIScreen mainScreen].applicationFrame.size.width - 140;
CGSize maxSize = CGSizeMake(labelWidth, MAXFLOAT);
CGSize labelSize;

Also, instead of using the UIScreen, simply use the width of the tableView, it makes your code less tightly coupled.

1
votes
You can use autolayout and set the height based on the content in the content view 
you need to set 

//swift

     override func viewDidLoad() 
        {
             self.tableView.estimatedRowHeight = 180  
        }

    // return  UITableViewAutomaticDimension 
      override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat 
        {
            return UITableViewAutomaticDimension
        }



//Objective C

        - (void)viewDidLoad
        {
            [super viewDidLoad];
            self.tableview.estimatedRowHeight = 180.0 ;

        }

        -(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        //constraint for autolayout for ios8+
        return UITableViewAutomaticDimension;
    }
0
votes

I used Joride's answer as a good starting point, and then pulled the label height functionality out into a separate function. I'm thinking that I'll abstract that one more notch and just return the actual CGSize for the label from that function so that I can cleanly call it from both heightForRowAtIndexPath and cellForRowAtIndexPath. I've included my updated code below.

cellForRowAtIndexPath:

   - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        NSInteger currentIndex = indexPath.row;
        if (currentIndex == 0){
            static NSString *CellIdentifier = @"MeasurementCell";
            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
            [[cell textLabel] setText:@"Eating"];
            [self.cellHeightList insertObject:[NSNumber numberWithInt:44] atIndex:currentIndex];
            return cell;
        } else if (currentIndex == 1){
            if (self.dataController.isScoreAvailable){
                static NSString *CellIdentifier = @"ScoreCell";
                UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
                [[cell textLabel] setText:@"Score"];
                [[cell detailTextLabel] setText:@"Score short description"];
                [self.cellHeightList insertObject:[NSNumber numberWithInt:46] atIndex:currentIndex];
                return cell;
            } else {
                static NSString *CellIdentifier = @"ScoreCell";
                UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
                [[cell textLabel] setText:@""];
                [[cell detailTextLabel] setText:@""];
                cell.accessoryType = UITableViewCellAccessoryNone;
                [self.cellHeightList insertObject:[NSNumber numberWithInt:0] atIndex:currentIndex];
                return cell;
            }
        } else if (currentIndex > 1){
            NSInteger labelWidth = [UIScreen mainScreen].applicationFrame.size.width - 140;
            CGSize maxSize = CGSizeMake(labelWidth, MAXFLOAT);

            static NSString *CellIdentifier = @"QuestionCell";
            InterviewQuestionCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
            InterviewQuestion *questionAtIndex = [self.dataController objectInListAtIndex:(currentIndex-2)];
            cell.questionLabel.text = questionAtIndex.questionText;
            CGSize labelSize = [cell.questionLabel.text sizeWithFont:[UIFont systemFontOfSize:12.0] constrainedToSize:maxSize lineBreakMode:NSLineBreakByWordWrapping];
            CGRect labelFrame = CGRectMake(0, 0, labelWidth, labelSize.height);
            cell.questionLabel.frame = labelFrame;
            cell.questionLabel.numberOfLines = 0;
            cell.answerControl.selectedSegmentIndex = questionAtIndex.answer;
            cell.answerControl.tag = indexPath.item;
            [self.cellHeightList insertObject:[NSNumber numberWithInt:labelSize.height] atIndex:currentIndex];
            return cell;
        }
        return nil;
    }

heightForRowAtIndexPath:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger currentIndex = indexPath.row;
    if (currentIndex == 0){
        return 44;
    } else if (currentIndex == 1){
        if (self.dataController.isScoreAvailable){
            return 46;
        } else {
            return 0;
        }
    } else if (currentIndex > 1){
        return [self getRowHeightForRow:currentIndex];
    }
    return 0;
}

new getRowHeightForRow function:

- (NSInteger)getRowHeightForRow:(NSInteger)currentRow{
    NSInteger labelWidth = [UIScreen mainScreen].applicationFrame.size.width - 140; //80 for segment + 3*20 for margins & spacing
    CGSize maxSize = CGSizeMake(labelWidth, MAXFLOAT);
    InterviewQuestion *questionAtIndex = [self.dataController objectInListAtIndex:(currentRow-2)];
    NSString *questionText = questionAtIndex.questionText;
    CGSize labelSize = [questionText sizeWithFont:[UIFont systemFontOfSize:12.0] constrainedToSize:maxSize lineBreakMode:NSLineBreakByWordWrapping];
    return labelSize.height;

}