[Ios] Using Auto Layout in UITableView for dynamic cell layouts & variable row heights


For IOS8 it's really simple:

override func viewDidLoad() {  

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension


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

But for IOS7, the key is calculate the height after autolayout,

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height


  • If multiple lines labels, don't forget set the numberOfLines to 0.

  • Don't forget label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

The full example code is here.


How do you use Auto Layout within UITableViewCells in a table view to let each cell's content and subviews determine the row height (itself/automatically), while maintaining smooth scrolling performance?

Let's say you have a cell with a subview, and you want the cell's height to be high enough to encompass the subview + padding.

1) Set the subview's bottom constraint equal to the cell.contentView minus the padding you want. Do not set constraints on the cell or cell.contentView itself.

2) Set either the tableView's rowHeight property or tableView:heightForRowAtIndexPath: to UITableViewAutomaticDimension.

3) Set either the tableView's estimatedRowHeight property or tableView:estimatedHeightForRowAtIndexPath: to a best guess of the height.

That's it.

I wrapped @smileyborg's iOS7 solution in a category

I decided to wrap this clever solution by @smileyborg into a UICollectionViewCell+AutoLayoutDynamicHeightCalculation category.

The category also rectifies the issues outlined in @wildmonkey's answer (loading a cell from a nib and systemLayoutSizeFittingSize: returning CGRectZero)

It doesn't take into account any caching but suits my needs right now. Feel free to copy, paste and hack at it.


#import <UIKit/UIKit.h>

typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);

 *  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
 *  Many thanks to @smileyborg and @wildmonkey
 *  @see .com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)

 *  Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
 *  @param name Name of the nib file.
 *  @return collection view cell for using to calculate content based height
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;

 *  Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
 *  @param block Render the model data to your UI elements in this block
 *  @return Calculated constraint derived height
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;

 *  Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;



#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width


    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;



Usage example:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);

Thankfully we won't have to do this jazz in iOS8, but there it is for now!

As long as your layout in your cell is good.

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

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

Update: You should use dynamic resizing introduced in iOS 8.

tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

In my case i have to create a custom cell with a image which is coming from server and can be of any width and height. And two UILabels with dynamic size(both width & height)

i have achieved the same here in my answer with autolayout and programmatically:

Basically above @smileyBorg answer helped but systemLayoutSizeFittingSize never worked for me, In my approach :

1. No use of automatic row height calculation property. 2.No use of estimated height 3.No need of unnecessary updateConstraints. 4.No use of Automatic Preferred Max Layout Width. 5. No use of systemLayoutSizeFittingSize (should have use but not working for me, i dont know what it is doing internally), but instead my method -(float)getViewHeight working and i know what it's doing internally.

Is it possible to have differing heights in a UITableView Cell when I use several different ways of displaying the cell?

I just did some dumb try and error with the 2 values of rowHeight and estimatedRowHeight and just thought it might provide some debugging insight:

If you set them both OR only set the estimatedRowHeight you will get the desired behavior:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

It's suggested that you do your best to get the correct estimate, but the end result isn't different. It will just affect your performance.

If you only set the rowHeight ie only do:

tableView.rowHeight = UITableViewAutomaticDimension

your end result would not be as desired:

If you set the estimatedRowHeight to 1 or smaller then you will crash regardless of the rowHeight.

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

I crashed with the following error message:

Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
    ...some other lines...

libc++abi.dylib: terminating with uncaught exception of type

Dynamic Table View Cell Height and Auto Layout

A good way to solve the problem with storyboard Auto Layout:

- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
  static RWImageCell *sizingCell = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];

  [sizingCell setNeedsLayout];
  [sizingCell layoutIfNeeded];

  CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
  return size.height;

The solution proposed by @smileyborg is almost perfect. If you have a custom cell and you want one or more UILabel with dynamic heights then the systemLayoutSizeFittingSize method combined with AutoLayout enabled returns a CGSizeZero unless you move all your cell constraints from the cell to its contentView (as suggested by @TomSwift here How to resize superview to fit all subviews with autolayout?).

To do so you need to insert the following code in your custom UITableViewCell implementation (thanks to @Adrian).

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
        [self.contentView addConstraint:contentViewConstraint];

Mixing @smileyborg answer with this should works.

Here is solution if it is containing text only:

private func estimateFrameForText(text: String) -> CGRect {
    let size = CGSize(width: 300.0, height: 1000)
    let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
    return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17.0)], context: nil)