UITableView flexible/dynamic heightForRowAtIndexPath

Yes, I agree it will already allocate the cell, but the heightForRowAtIndexPath delegate method is only called for the cells that will be visible so the cell will be allocated anyway.

This is incorrect. The table view needs to call heightForRowAtIndexPath (if it's implemented) for all rows that are in the table view, not just the ones currently being displayed. The reason is that it needs to figure out its total height to display the correct scroll indicators.


I used to do this by:

  1. Creating a collection objects (array of size information (dictionary, NSNumber of row heights, etc.) based on the collection objects that will be used for the table view.

  2. This is done when we're processing the data either from a local or remote source.

  3. I predetermine the type and size of the font that will be used, when I'm creating this collection objects. You can even store the UIFont objects or whatever custom objects used to represent the content.

  4. These collection objects will be used every time I implement UITableViewDataSource or UITableViewDelegate protocols to determine the sizes of the UITableViewCell instances and its subviews, etc.

By doing it this way you can avoid having to subclass UITableViewCell just to get the various size properties of its content.

Don't use an absolute value for initializing the frames. Use a relative value based on the current orientation and bounds.

If we rotate it to any orientation, just do a resizing mechanism at runtime. Make sure the autoresizingMask is set correctly.

You only need the heights, you don't need all of that unnecessary things inside a UITableViewCell to determine the row height. You may not even need the width, because as I said the width value should be relative to the view bounds.


Here is my approach for solving this

  1. I assume in this solution that only one Label has a "dynamic" height
  2. I also assume if we make the label auto size to stretch the height as the cell grows only the cell height is needed to change
  3. I assume that the nib has the appropriate spacing for where the label will be and how much space is above and bellow it
  4. We dont want to change the code every time we change the font or position of the label in the nib

How to update the height:

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

    // We want the UIFont to be the same as what is in the nib,
    //  but we dont want to call tableView dequeue a bunch because its slow. 
    //  If we make the font static and only load it once we can reuse it every
    //  time we get into this method
    static UIFont* dynamicTextFont;
    static CGRect textFrame;
    static CGFloat extraHeight;
    if( !dynamicTextFont ) {
        DetailCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
        dynamicTextFont = cell.resizeLabel.font;
        CGRect cellFrame = cell.frame;
        textFrame = cell.resizeLabel.frame;
        extraHeight = cellFrame.size.height-textFrame.size.height; // The space above and below the growing field
    }
    NSString* text = .... // Get this from the some object using indexPath 

    CGSize  size = [text sizeWithFont:dynamicTextFont constrainedToSize:CGSizeMake(textFrame.size.width, 200000.f) lineBreakMode:UILineBreakModeWordWrap];

    return size.height+extraHeight;
}

Issues:

  • If you are not using a prototype cell you will need to check if the cell is nil and init it
  • Your nib / storyboard must have the UILabel autosize and have multi line set to 0

You should have a look at TTTableItemCell.m in the Three20 framework. It follows a different approach, basically by having each cell class (with some predefined settings like font, layout etc.) implement a shared method + tableView: sizeForItem: (or something like that), where it gets passed the text in the item object. When you look up the text for a specific cell, you can as well look up the appropriate font, too.

Regarding the cell height: You can check your tableView's width and, if necessary, subtract the margins by UITableViewStyleGrouped and the width an eventual index bar and disclosure item (which you look for in the data storage for your cells' data). When the width of the tableView changes, e.g. by interface rotation, you have to call [tableView reloadData].