Calculate Content Bounds for i.e. dynamic cell heights

suggest change

A common use case for wanting to calculate the frame a label will take up is for sizing table view cells appropriately. The recommended way of doing this is using the NSString method boundingRectWithSize:options:attributes:context:.

options takes String drawing options:

attributes is an NSDictionary of attributes that effect attributed strings (full list: Apple Docs) but the factors that effect height include:

context should be nil since the primary NSStringDrawingContext use case is for allowing font to resize to fit a specified rect, which shouldn’t be the case if we’re calculating a dynamic height.

Objective C

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

    NSString *labelContent = cell.theLabel.text;
    // you may choose to get the content directly from the data source if you have done minimal customizations to the font or are comfortable with hardcoding a few values
//    NSString *labelContent = [self.dataSource objectAtIndexPath:indexPath];
    
    // value may be hardcoded if retrieved from data source
    NSFont *labelFont = [cell.theLabel font];
    
    // The NSParagraphStyle, even if you did not code any changes these values may have been altered in IB
    NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
    paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; 
    paragraphStyle.alignment = NSTextAlignmentCenter;

    NSDictionary *attributes = @{NSFontAttributeName: labelFont,
                                 NSParagraphStyleAttributeName: paragraphStyle};

    // The width is also important to the height
    CGFloat labelWidth = CGRectGetWidth(cell.theLabel.frame);
    // If you have been hardcoding up to this point you will be able to get this value by subtracting the padding on left and right from tableView.bounds.size.width
//    CGFloat labelWidth = CGRectGetWidth(tableView.frame) - 20.0f - 20.0f;

    CGRect bodyBounds = [labelContent boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];

    return CGRectGetHeight(bodyBounds) + heightOfObjectsOnTopOfLabel + heightOfObjectBelowLabel;
}

Swfit 3

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    var cell = tableView.cellForRow(atIndexPath: indexPath)!
    var labelContent = cell.theLabel.text
    var labelFont = cell.theLabel.font
    var paragraphStyle = NSMutableParagraphStyle()

    paragraphStyle.lineBreakMode = .byWordWrapping
    paragraphStyle.alignment = .center

    var attributes = [NSFontAttributeName: labelFont, NSParagraphStyleAttributeName: paragraphStyle]

    var labelWidth: CGFloat = cell.theLabel.frame.width

    var bodyBounds = labelContent.boundingRect(withSize: CGSize(width: width, height: CGFLOAT_MAX), options: .usesLineFragmentOrigin, attributes: attributes, context: nil)

    return bodyBounds.height + heightOfObjectsOnTopOfLabel + heightOfObjectBelowLabel
}

Conversely, if you do have a set maximum number of lines you will first need calculate the height of a single line to make sure we don’t get a value taller than the allowed size:

// We calculate the height of a line by omitting the NSStringDrawingUsesLineFragmentOrigin option, which will assume an infinitely wide label
CGRect singleLineRect = [labelContent boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
                                             options:NSStringDrawingTruncatesLastVisibleLine
                                             context:nil];
CGFloat lineHeight = CGRectGetHeight(singleLineRect);
CGFloat maxHeight = lineHeight * cell.theLabel.numberOfLines;

// Now you can call the method appropriately
CGRect bodyBounds = [labelContent boundingRectWithSize:CGSizeMake(width, maxHeight) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingTruncatesLastVisibleLine) attributes:attributes context:nil];

return CGRectGetHeight(bodyBounds) + heightOfObjectsOnTopOfLabel + heightOfObjectBelowLabel;

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you:


UILabel:
* Calculate Content Bounds for i.e. dynamic cell heights

Table Of Contents
1 UILabel
12 UIView
15 UIColor
26 UIImage
28 CALayer
30 NSDate
40 iBeacon
49 NSTimer
79 NSURL
87 AWS SDK
96 NSData
101 Segues
104 EventKit
105 NSBundle
106 SiriKit
111 StoreKit
117 3D Touch
119 Keychain
122 Block
141 AirDrop
144 UISlider
145 Carthage
146 HealthKit
151 plist
157 MVVM
164 UIPhoenix
166 Simulator
168 NSArray
169 OpenGL
175 Core Data
179 MyLayout
180 UIFont
189 Security
200 Codable