3
votes

I have a view controller which has a UITableView instance and uses auto layout constraints to render it. I would like to have variable cell heights in this table view. I do not want to calculate the cell height myself because I have a complex cell content composed of several labels and images that can vary from cell to cell. I do believe it is possible to let the auto-layout cell to resize itself so it contains all its subviews (i.e. using sizeToFit method of labels after assigning text to them?).

I have a custom cell class that uses visual constraints format of auto layout to position its subviews. I tried to incorporate the method proposed in here and its sample implementation here.

When I initialize the table view, I create an array with the equal length of my data rows and calculate each row's height by assigning values to a prototype cell of type MyCustomCell and retrieve its height using this

[cell systemLayoutSizeFittingSize:UILayoutFittingExpandedSize].height

and storing it in the heights array so to use it later in heightForRowAtIndexPath method of table view to retrieve the correct cell height for individual cells.

Doing all these, however, I end up with an unreadable exception NSInternalInconsistencyException in xCode with "Cannot find an outgoing row head for incoming head MyCustomCell:0xa8a1430.Width, which should never happen."

Here is the initialization of the subviews in my custom cell:

    _titleLabel = [[UILabel alloc] init];
    _titleLabel.font = [TSTheme boldThemeFontOfSize:TSThemeFontSizeSmall];
    _titleLabel.textColor = [[TSTheme sharedTheme] darkTextColor];
    _titleLabel.backgroundColor = [UIColor clearColor];
    _titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
    _titleLabel.font = [UIFont fontWithName:@"HelveticaNeue-CondensedBold" size:19];
    _titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
    [self.contentView addSubview:_titleLabel];

    _summaryLabel = [[UILabel alloc] init];
    _summaryLabel.font = [TSTheme boldThemeFontOfSize:TSThemeFontSizeSmall];
    _summaryLabel.backgroundColor = [UIColor clearColor];
    _summaryLabel.textColor = [[TSTheme sharedTheme] darkTextColor];
    _summaryLabel.translatesAutoresizingMaskIntoConstraints = NO;
    _summaryLabel.numberOfLines = 0;
    _summaryLabel.preferredMaxLayoutWidth = 250.0f; // required for text wrapping
    _summaryLabel.font = [UIFont fontWithName:@"Georgia" size:14];
    _summaryLabel.lineBreakMode = NSLineBreakByWordWrapping;
    [self.contentView addSubview:_summaryLabel];

    _thumbnailView = [[UIImageView alloc] init];
    _thumbnailView.translatesAutoresizingMaskIntoConstraints = NO;

    [self.contentView addSubview:_thumbnailView];

and the constraints for my custom cell are as follow

NSDictionary *views = NSDictionaryOfVariableBindings(_titleLabel, _summaryLabel, _thumbnailView);
NSDictionary *metrics = @{@"margin": @"5"};

[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(margin)-[_thumbnailView(<=60)]-(margin)-[_titleLabel]-(margin)-|"
                                                                         options:0
                                                                         metrics:metrics
                                                                           views:views]];

[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(margin)-[_thumbnailView]-(margin)-|"
                                                                         options:0
                                                                         metrics:metrics
                                                                           views:views]];


[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(margin)-[_titleLabel]-(0)-[_summaryLabel]"
                                                                         options:0
                                                                         metrics:metrics
                                                                           views:views]];


[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(margin)-[_thumbnailView]-(margin)-[_summaryLabel]-(margin)-|"
                                                                         options:0
                                                                         metrics:metrics
                                                                           views:views]];
3

3 Answers

0
votes

I encountered this when I used a table view cell that was added directly as a prototype to a UITableView in a storyboard and used dequeueReusableCellWithIdentifier: to get the cell for caching the heights.

If I moved the cell into a separate nib then it instantiated the cell for determining heights directly from the nib the problem went away.

Then you can register the nib with the UITableView. Hope this helps.

0
votes

I used this code to calculate dinamically the hieght of a row because the content of each one was different:

-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ // Hacer que el tamaño de la celda sea variable dependiendo del tamaño del texto a mostrar

    NoticiaAsignatura_DTO *rowData = [[NoticiaAsignatura_DTO alloc] init];

    rowData = [self.listaNoticias objectAtIndex:[indexPath row]];

    NSString *text = rowData.title;
    text = [text stringByAppendingString:@"\n"];
    text = [text stringByAppendingString: rowData.publish_date_ansi];
    text = [text stringByAppendingString:@"\n\n"];
    text = [text stringByAppendingString:rowData.content];
    text = [text stringByAppendingString:@"\n\n"];

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){

        CGSize constraint = CGSizeMake(700,200000.0);
        CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:NSLineBreakByWordWrapping];
        return size.height + (CELL_CONTENT_MARGIN * 2);
    }
    else{
        CGSize constraint = CGSizeMake(300,200000.0);
        CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE_PHONE] constrainedToSize:constraint lineBreakMode:NSLineBreakByWordWrapping];
        NSLog(@"Size: %f ", size.height);
        return size.height + (CELL_CONTENT_MARGIN * 2);
    }

}

Hope it helps.

0
votes
  • Assign and implement tableview dataSource and delegate
  • Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
  • Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)

-

Objective C:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.table.dataSource = self;
    self.table.delegate = self;

    self.table.rowHeight = UITableViewAutomaticDimension;
    self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}

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

    return UITableViewAutomaticDimension;
}

Swift:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Don't forget to set dataSource and delegate for table
    table.dataSource = self
    table.delegate = self

    // Set automatic dimensions for row height
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension
}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

For label instance in UITableviewCell

  • Set number of lines = 0 (& line break mode = truncate tail)
  • Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
  • Optional: Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.

enter image description here

Note: If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.