1.In your UITableViewCell subclass, add constraints so that the subviews of the content view have their edges pinned to the edges of the content view (most importantly to the top AND bottom edges). Let the intrinsic content size of these subviews drive the height of the table view cell's content view by making sure the content compression resistance and content hugging constraints in the vertical dimension for each subview are not being overridden by higher-priority constraints you have added.
Remember the idea is to have the subviews connected vertically to the cell's content view so that they can "exert pressure" and make the content view expand to fit them.
If you're adding constraints in code, you should do this once after init in the updateConstraints method.
2.For every unique set of constraints in the cell, use a unique cell reuse identifier. In other words, if your cells have more than one unique layout, each unique layout should receive its own reuse identifier.
For example, if you were displaying an email message in each cell, you might have 4 unique layouts: messages with just a subject, messages with a subject and a body, messages with a subject and a photo attachment, and messages with a subject, body, and photo attachment. Each layout has completely different constraints required to achieve it, so once the cell is initialized and the constraints are added for one of these layouts, the cell should get a unique reuse identifier specific to that layout. This means when you dequeue a cell for reuse, the constraints have already been added and are ready to go for that layout.
Note that due to differences in intrinsic content size, cells with the same constraints (layout) may still have varying heights! Don't confuse fundamentally different layouts (different constraints) with different calculated view frames (solved from identical constraints) due to different sizes of content.
Do not add cells with completely different sets of constraints to the same reuse pool (i.e. use the same reuse identifier) and then attempt to remove the old constraints and set up new constraints from scratch after each dequeue. The internal Auto Layout engine is not designed to handle large scale changes in constraints, and you will see massive performance issues.
After configuring the cell with the content it will hold, force the cell to immediately layout its subviews, and then use the systemLayoutFittingSize: method on the UITableViewCell's contentView to find out what the required height of the cell is. Use UILayoutFittingCompressedSize to get the smallest size required to fit all the contents of the cell. The height can then be returned by the tableView:heightForRowAtIndexPath: delegate method.
If your table view has more than a couple dozen rows in it, you will find that doing the Auto Layout constraint solving can quickly bog down the main thread when first loading the table view, as tableView:heightForRowAtIndexPath: is called on each and every row upon first load (in order to calculate the size of the scroll indicator).
As of iOS 7, you can and absolutely should use the estimatedRowHeight property on the table view, or implement the delegate method tableView:estimatedHeightForRowAtIndexPath:. What this does is allow you to return a guess for the cell height using minimal or no computation, which is used to get a temporary estimate/placeholder for the row height of cells that are not onscreen. Then, when these cells are about to scroll onscreen, the real row height will be calculated and the estimated height updated with the actual one.
Generally speaking, the estimate you provide doesn't have to be very accurate - it's only used to correctly size the scroll indicator in the table view, and the table view does a good job of adjusting it when you scroll and it finds that your estimates were wrong. I would just suggest adding enough logic to make sure your estimated heights are within an order of magnitude of the actual heights.
If you've done all the above and are still finding that performance is unacceptably slow (when doing the constraint solving for the row height calculations), you'll unfortunately need to implement some caching for cell heights. The general idea is to let the Auto Layout engine solve the constraints the first time, then cache the calculated height for that cell and use the cached value for all future requests for that cell's height. The trick of course is to make sure you clear the cached height for a cell when anything happens that could cause the cell's height to change -- primarily, this would be when that cell's content changes or when other important events occur (like the user adjusting the Dynamic Type text size slider).
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Dequeue a cell for the particular layout required (you will likely need to substitute
// the reuse identifier dynamically at runtime, instead of a static string as below).
// Note that this method will init and return a new cell if there isn't one available in the reuse pool,
// so either way after this line of code you will have a cell with the correct constraints ready to go.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UniqueCellIdentifierForThisUniqueCellLayout"];
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return a fixed constant if possible, or do some minimal calculations if needed to be able to return an
// estimated row height that's at least within an order of magnitude of the actual height.
// For example:
if ([self isLargeTypeForCellAtIndexPath:indexPath]) {
return 150.0f;
} else {
return 60.0f;
}
}