24

I have a UITableView that has five static cells. I need the cell height of one cell to adjust automatically to its contents, which is one UILabel.

Is there any way I can use..

tableView.estimatedRowHeight = 42.0
tableView.rowHeight = UITableViewAutomaticDimension

..for a table view with static cells, or does this only work for table views with dynamic prototype cells?

(Or is there any other way of doing this that you would recommend?)

Additional info

The UITableView is in a TableViewController which is embedded into a Container View.

The five static cells are quite different from one another and are only used in one app scene, so I don't see much point in going for dynamic prototypes and custom cell subclasses.

I update the tableview like this

-(void) viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [_myTableView reloadData];
}

I add constraints to the UILabel as described here: http://www.appcoda.com/self-sizing-cells/

camelBase
  • 917
  • 1
  • 8
  • 16
  • Did you find out anything more? Cause I'd also like to use `UITableViewAutomaticDimension` with a static TableView... – Georg Nov 06 '14 at 10:25
  • I've posted an [answer](http://stackoverflow.com/a/40678072/1966109) that shows up to 4 different ways for a static `UITableViewCell` to automatically adjust its height to its contents with Swift 3. – Imanou Petit Nov 20 '16 at 10:34

8 Answers8

45

For iOS 8, I found out that you cannot do the same strategy as for Dynamic Table View Cell.

To automatically adjust the height of static cells, I implement these two UITableViewDelegate methods:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewAutomaticDimension;
}

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 44;
}

Hope it helps.

Victor Chandra
  • 511
  • 5
  • 5
  • I spent all day trying to figure this one out! Thank you so much! Oh and a note: in this case custom row heights work no longer, and as such you must set all your constraints such that they derive the cell height. For instance: i had a cell with a fixed size container view iniside, and for that one i set the "center y in superview" constraint, and added a custom height to the cell. The custom height was ignored, and as such I had to remove the "center y"constraint and add top and bottom spacing constraints instead. :) – dec-vt100 Mar 23 '15 at 18:59
  • I tested it on iOS 9 and you can use custom height with UITableViewAutomaticDimension, in method heightForRowAtIndexPath return custom height for row that you need it and in other case return UITableViewAutomaticDimension. – Michal Gumny Apr 12 '17 at 12:22
4

Expanding over Victors' answer, Static Cells based table views seem to autosize cells based on content and constraints once the following delegates are implemented:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewAutomaticDimension;
}

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 44;
}

EXCEPT for when there are labels. For some reason the label intrinsic content heights do not seem to contribute towards the height calculation of the cell when they have . My current work around for this is to nest the labels in an UIView.

Do the following:

  1. Embed the label (or labels) in a view. (Select label(s) on IB and hit Menu > Editor > Embed In > View)
  2. Establish horizontal and vertical margin constraints between this view and the cell
  3. Establish horizontal and vertical margin constrains between your label(s) and this view.

Certainly feels like a hack but works for me in all the cases I have tried.

Apurva
  • 86
  • 4
1

Ended up doing this. Not what I wanted, but it kind of works for my simple requirements and text lengths.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 2)
    {
        CGFloat padding = 30.0f;

        CGFloat labelWidth = self.tableView.bounds.size.width - padding*2;
        NSAttributedString *text = [[NSAttributedString alloc] initWithString:_freeTextLabel.text];
        NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin;
        CGRect boundingRect = [text boundingRectWithSize:CGSizeMake(labelWidth, CGFLOAT_MAX)
                                             options:options
                                             context:nil];

        return (CGFloat) (ceil(boundingRect.size.height) + padding*2);
    }
    return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
camelBase
  • 917
  • 1
  • 8
  • 16
1

NOTE: This is based on @CamelBase 's answer, which is written for Objective-C, so mostly just a conversion to Swift for those that made the switch and still struggle converting stuff themselves.


For those wanting to do this using Swift that want a table with static cells, here's a working solution.

I didn't need to use this code (tried putting it in viewDidLoad() and tried without, made no difference):

tableView.estimatedRowHeight = 42.0
tableView.rowHeight = UITableViewAutomaticDimension

First you want to add UILabel to each cell you want auto-grow content in.

You'll need to add 4 constraints to the UILabel all relative to the parent (Content View of the cell).

  • Leading Margin 0
  • Trailing Margin 0
  • Top Margin 0
  • Bottom Margin 0

This will make the label grow when the cell grows (which our code below does).

Now just add this code:

override func tableView(tableView: UITableView, 
heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat 
{
    let sectionICareAbout = 2
    let rowICareAbout = 1

    // repeat for as many section/row combos you need dynamic sizing on
    if indexPath.section == sectionICareAbout 
      && indexPath == rowICareAbout {
        // replace with how you get the content for the cell
        // in section 2 row 1
        let text = "line1\nline2\nline3"
        return rowHeightForText(text)
    }

    // use auto-dimension if we didn't set something else
    return UITableViewAutomaticDimension
}

func rowHeightForText(text: String) -> CGFloat {
    let labelWidth = tableView.bounds.size.width
    let attributedText = NSAttributedString(string: text)
    let options = NSStringDrawingOptions.UsesLineFragmentOrigin
    let size = CGSize(width: labelWidth, height: CGFloat.max)
    let boundingRect = attributedText.boundingRectWithSize(size, 
        options: options, context: nil)
    return boundingRect.size.height
}
Timothy Walters
  • 16,866
  • 2
  • 41
  • 49
1

In IOS8, I've found that explicitly setting the row height/width or label height/width in code causes more problems than it fixes. I have created dynamic and static tables with varying cell height and multiline labels using autolayout, but you really have to follow Apple standard to the letter or weird things happen (like separators disappearing or rows randomly collapsing in height).

  • [edited] Set estimatedHeightForRowAtIndexPath and heightForRowAtIndexPath to automatic dimensions in your UITableViewDelegate

    override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }
    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }
    
  • Make sure every single element in the cell has a top/bottom/left/right = constraint (not >=, <=, or alignX or alignY - it has to be =). You can make this a low priority constraint and provide a better constraint of higher priority, but you have to give it an exact starting value to size the cell.

E. Ivie
  • 91
  • 1
  • 7
1

Here is a simple example with Swift 3. We have a UITableViewController in Storyboard set with type grouped and content of type static cells. Its tableView contains one UITableViewCell. The cell contains one UILabel that has a number of lines set to 0 and some very long text to display. The label also has four Auto layout constraints with its superView (top, bottom, leading and trailing).

enter image description here

Now, you may choose one of the four following code snippets in order to allow your static cell to automatically resize itself according to its content.


#1. Using tableView(_:estimatedHeightForRowAt:) and tableView(_:heightForRowAt:)

Note: tableView(_:estimatedHeightForRowAt:) method requires iOS7

import UIKit

class TableViewController: UITableViewController {

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }

    override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100 // Return `UITableViewAutomaticDimension` if you have no estimate
    }

}

#2. Using estimatedRowHeight property and tableView(_:heightForRowAt:)

Note: estimatedRowHeight property requires iOS7

import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.estimatedRowHeight = 100
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }

}

#3. Using tableView(_:heightForRowAt:)

import UIKit

class TableViewController: UITableViewController {

    // Bind this IBOutlet to the cell in Storyboard
    @IBOutlet weak var cell: UITableViewCell!

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let fittingSize = CGSize(width: tableView.bounds.size.width, height: 0)
        let systemLayoutSize = cell.systemLayoutSizeFitting(fittingSize, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel)
        return systemLayoutSize.height
    }

}

#4. Using UITableViewCell subclass and tableView(_:heightForRowAt:)

import UIKit

class TableViewController: UITableViewController {

    // 1. Set custom class to `CustomCell` for cell in Storyboard
    // 2. Bind this IBOutlet to the cell in Storyboard
    @IBOutlet weak var cell: CustomCell!

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        cell.layoutIfNeeded()
        let contentViewSize = cell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
        return contentViewSize.height + 1 // + 1 for the separator
    }

}

class CustomCell: UITableViewCell {

    // 1. Set custom class to `CustomCell` for cell in Storyboard
    // 2. Bind this IBOutlet to the cell's label in Storyboard
    @IBOutlet weak var label: UILabel!

    override func layoutSubviews() {
        super.layoutSubviews()
        label.preferredMaxLayoutWidth = frame.width
    }

}

All four previous code snippets will result in the following display:

Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
0

I assume that I would need to tell the cell to redraw using something like

[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:@[indexPathOfYourCell] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];

When it comes to dynamically changing the height of the tableviewcells.

-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return Yourvalue; //return whatever u want ur cell to have size.
  }
  • Thank you for your answer. I can't get the redraw to work, but I will look into that some more. For heightForRowAtIndexPath, I thought that method would not be called for static cells, but I was wrong about that. It does however require some size calculations (as seen [here](http://stackoverflow.com/questions/11981071/dynamic-height-for-static-table-cells-with-wrapping-labels)) which would be the reason I initially wanted to use UITableViewAutomaticDimension – camelBase Oct 01 '14 at 11:14
  • Managed to get reloadRowsAtIndexPaths run without errors and it does not solve the problem. Thanks anyway @DorusJanssens! – camelBase Oct 01 '14 at 12:25
0

I was looking for an answer to this as well and found this solution, which i tested and works perfectly in iOS8.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row == 2)  //Dynamic Height
    {
        return UITableViewAutomaticDimension;
    }
    else 
    {
        return 50;  // Static Height
    }
}
shokaveli
  • 478
  • 7
  • 14