24
votes

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/

8
Did you find out anything more? Cause I'd also like to use UITableViewAutomaticDimension with a static TableView... - Georg
@Georg, see my answer. - camelBase
I've posted an answer that shows up to 4 different ways for a static UITableViewCell to automatically adjust its height to its contents with Swift 3. - Imanou Petit

8 Answers

45
votes

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.

4
votes

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.

1
votes

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];
}
1
votes

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
}
1
votes

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.

1
votes

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:

0
votes

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.
  }
0
votes

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
    }
}