dynamic calculation of UILabel width in UITableViewCell

21,813

Solution 1

Explicitly setting the font solves your problem.

CGSize textLabelSize = [textLabel.text sizeWithFont:[UIFont systemFontOfSize:17]];

Gory Detals:
When the tableview first loads, this snippet returns zero width due to the font size being 0.

[textLabel.text sizeWithFont:textLabel.font]

I figured this out by displaying the point size of the cell font.

NSLog(@"font size: %f", cell.textLabel.font.pointSize);

The point size was zero until the cell went off screen and came back, just as you described.

Solution 2

I completely agree that it's sufficiently annoying that it should be a bug. Why would the nice folks at Apple ever ask for the cell height without ensuring the cell is completely set up. Silly! However, instead of using a deprecated function, try:

[cell layoutIfNeeded];

Calling this method has the same effect with the cell being set up including the bounds, cell font, etc. Therefore, any calculation after this has access to the fully set up cell. Here's the whole code:

-(CGFloat) tableView:(UITableView *) tableView heightForRowAtIndexPath:(NSIndexPath *) indexPath {

  // Find the cell for this index path
  UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
  CGFloat cellHeight = cell.frame.size.height;

  // Calculate text size after forcing a layout
  [cell layoutIfNeeded];
  CGSize textSize = [cell.textLabel.text sizeWithFont:cell.textLabel.font constrainedToSize:CGSizeMake(cell.contentView.bounds.size.width, 99999) lineBreakMode:cell.textLabel.lineBreakMode];

  // Only change the cell height if the text forces a growth
  if (textSize.height > cellHeight) {
    cellHeight = textSize.height;
  }

  return cellHeight;
}

Solution 3

Use [myLbl sizeToFit]; to automatically resize the label.

Here is the sample code.

UILabel *lbl1, *lbl2;
    if (cell == nil) {

   UILabel *lbl1 = [[UILabel alloc] init];
            [lbl1 setFont:[UIFont boldSystemFontOfSize:20]];
            [cell.contentView addSubview:lbl1];
            lbl1.frame = CGRectMake(3, 10, 0, 0);
            lbl1.tag = 10;
            [lbl1 release];

        UILabel *lbl2 = [[UILabel alloc] init];
        [lbl2 setFont:[UIFont systemFontOfSize:20]];
        [cell.contentView addSubview:lbl2];                 
        lbl2.tag = 20;
        [lbl2 release];
}
else {
       lbl1 = (UILabel*)[cell.contentView viewWithTag:10];
       lbl2 = (UILabel*)[cell.contentView viewWithTag:20];
}

  lbl1.text = @"Hello ";
    [lbl1 sizeToFit];

lbl2.text = @"World";
        [lbl2 sizeToFit];
        lbl2.frame = CGRectMake(lbl1.frame.origin.x+lbl1.frame.size.width+5,
                                lbl1.frame.origin.y,
                                lbl2.frame.size.width,
                                lbl1.frame.size.height);

Solution 4

I found the same issue, but I've found a workaround. FYI I've raised an bug with Apple (Bug ID# 7535066). Lex, in your example, if you use cell.font instead of textLabel.font, it should work, although you will get a compiler warning, as this is a deprecated method call. I'm not sure if you'll be able to see the details of the bug I raised, so I'm pasting the details here:

iPhone SDK 3.1.2 (7D11) NSString UIKit Additions method sizeWithFont returns nil

SUMMARY I am decorating UITableViewCells in the UITableView delegate method:

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

I wish to find length of the text already in the cell, to correctly format the new view, so I am calling the NSString UIKit Additions method:

  • (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size

When I use cell.textLabel.font for the font argument, it always returns an empty CGSize

IF I use the DEPRECATED method, cell.font for the argument, it DOES return the CGSize value as expected.

STEPS TO REPRODUCE 1. Create a UITableViewController with a TableView, and populate it with some sample data 2. In the stubbed method cellForRowAtIndexPath, after the comment //Set up the cell, and where the cell.textLabel.text is populated, put the following:

  
 CGSize size = [cell.textLabel.text sizeWithFont:[[cell textLabel] font] constrainedToSize:CGSizeMake(320, 480)];
 NSLog(@"cell frame: %f %f", size.width, size.height);

 CGSize size2 = [cell.textLabel.text sizeWithFont:[cell font] constrainedToSize:CGSizeMake(320, 480)];
 NSLog(@"DEPRECATED cell frame: %f %f", size2.width, size2.height);

EXPECTED RESULTS

If anything, you would expect the call that uses the Deprecated method to retrieve the font, to fail, or at least both to return the same result.

ACTUAL RESULT:

2010-01-12 22:06:36.645 PrefTest[9659:207] cell frame: 0.000000 0.000000 2010-01-12 22:06:36.646 PrefTest[9659:207] DEPRECATED cell frame: 88.000000 24.000000

WORKAROUND & ISOLATION:

As noted, use the deprecated method to get the font. But it gets stranger!

Reverse the two methods I gave above:

 
 CGSize size2 = [cell.textLabel.text sizeWithFont:[cell font] constrainedToSize:CGSizeMake(320, 480)];
 NSLog(@"DEPRECATED cell frame: %f %f", size2.width, size2.height);

    CGSize size = [cell.textLabel.text sizeWithFont:[[cell textLabel] font] constrainedToSize:CGSizeMake(320, 480)];
 NSLog(@"cell frame: %f %f", size.width, size.height);

The results are now:

2010-01-12 22:06:36.645 PrefTest[9659:207] cell frame: 88.000000 24.000000 2010-01-12 22:06:36.646 PrefTest[9659:207] DEPRECATED cell frame: 88.000000 24.000000

It seems that just referencing [cell font] is enough to force [[cell textLabel] font] to work correctly.

Share:
21,813

Related videos on Youtube

Leonard
Author by

Leonard

Updated on July 09, 2022

Comments

  • Leonard
    Leonard almost 2 years

    I am building a custom, in-application settings view based on UITableViewCellStyle1 (or so).

    I am trying to dynamically calculate the width of the left label (title) of a cell to determine how wide my text field on the right can be.

    When I launch the app in the simulator and the view loads, the width of the title labels is zero until I scroll down the view and a cell is not visible, then when I scroll back up the size adjusts as expected. I believe this might have to do something with cell reusage.

    I need the title labels in the cells to have their correct width when the view loads, not after they have gone out of sight one time.

    - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
      {
          static NSString* reuseIdentifier = @"SettingsCell";
    
    UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    
    if ( !cell )
    {   
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier] autorelease];
        [cell setBackgroundColor:[UIColor baeDarkGrayColor]];
    
    
        UILabel* cellLabel = [cell textLabel];
        [cellLabel setTextColor:[UIColor whiteColor]];
        [cellLabel setBackgroundColor:[UIColor clearColor]];
    
        [cell setUserInteractionEnabled:YES];
        [cell setSelectionStyle:UITableViewCellSelectionStyleGray];
    }
    
    // Populate cell with corresponding data.
    NSDictionary* tableSection = [_tableData objectAtIndex:indexPath.section];
    
    // Set the label
    UILabel* textLabel = [cell textLabel];
    NSString* labelString = [[tableSection objectForKey:@"itemTitles"] objectAtIndex:indexPath.row];
    [textLabel setText:labelString];
    
       //   CGSize constrainedSize;
      //    constrainedSize.width = MAXFLOAT;
     // constrainedSize.height = MAXFLOAT;
    
    CGSize textLabelSize = [textLabel.text sizeWithFont:textLabel.font /*constrainedToSize:constrainedSize*/];
    
    NSLog(@"text label width: %f", textLabelSize.width);    
    
    
    // Set the accessory view
    BAESettingsTableCellAccessoryType accessoryType = [[[tableSection objectForKey:@"itemAccessories"] objectAtIndex:indexPath.row] integerValue];
    switch ( accessoryType )
    {
        case BAESettingsTableCellAccessoryDisclosureIndicator:
        {
            UIImageView* disclosureView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"AccessoryDisclosure.png"]];
            [cell setAccessoryView:disclosureView];
            [disclosureView release];
            break;
        }
        case BAESettingsTableCellAccessoryOnOffSwitch:
        {
            UISwitch* onOffSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
            [onOffSwitch setOn:YES];
            [cell setAccessoryView:onOffSwitch];
            [onOffSwitch release];
            break;
        }
        case BAESettingsTableCellAccessoryNone:
            // default:
            // break;
        {
    
            CGRect cellFrame = [cell frame];
    
            float detailTextFieldWidth = cellFrame.size.width - ( textLabelSize.width + 50 );
            float detailTextFieldHeight = cellFrame.size.height - 1;
    
            NSLog(@"detail text field calculated frame width, height, %f, %f", detailTextFieldWidth, detailTextFieldHeight);
    
    
            CGRect frame = CGRectMake(cellFrame.origin.x, cellFrame.origin.y, detailTextFieldWidth, detailTextFieldHeight);
    
    
            UITextField* textField = [[UITextField alloc] initWithFrame:frame];
            NSString* detailLabelString = [[tableSection objectForKey:@"itemDetailStrings"] objectAtIndex:indexPath.row];
            textField.text = detailLabelString;
            textField.textColor = cell.detailTextLabel.textColor;
            textField.textAlignment = UITextAlignmentRight;
            textField.borderStyle = UITextBorderStyleRoundedRect;
            [cell setAccessoryView:textField];
            [textField release];
            break;
        }
    
    }
    
    return cell;
       }
    
  • Vyacheslav
    Vyacheslav about 10 years
    Imazing idea. [cell layoutIfNeeded]; works great! The other solutions doesn't work. Thanks a lot!