Self Sizing Table View Cells

Dynamic Type Everywhere

I covered the basics of supporting dynamic type some time ago. Introduced in iOS 7, dynamic type allows the user to specify a preferred reading size for text. In the upcoming release of iOS 8 the Apple internal apps will adopt dynamic type and it seems they are making a big push to encourage all apps to do the same.

Supporting dynamic type is not without cost. You need to consider the impact when a user changes their text size preference. For example you may need to adjust the row height of a table view as the text size changes. I covered one approach to create Table View Cells With Varying Row Heights a while ago.

That example targeted iOS 7 and used auto layout for the cell content view. It needed a prototype cell to layout and size a cell from within the table view delegate method tableView:heightForRowAtIndexPath:. You can go back and read that earlier post or browse the example code if you want to learn more. In this post we will look at how to simplify that example code with some changes that Apple is introducing in iOS 8.

I am using iOS 8 beta 5 at the time I write this. There are still some rough edges which could change before the final release.

Self Sizing Cells

The WWDC 2014 Session 226 What’s New in Table and Collection Views covers a way in iOS 8 to create self-sizing cells in both collection and table views. This removes the need to use tricks such as a prototype cell to get cell heights. I will walk through a working example but if you want the one line summary:

Use estimatedRowHeight not rowHeight in iOS 8

Sample Code

I am going to use the original example code as the starting point so go back and read that post if you want the step by step instructions. As we will see the only code that will change is the table view controller and the storyboard. In brief, I am using the first fifteen chapters of Huckleberry Finn as a source of text with varying line length. A table view shows each line of text together with a label showing the line number:

Both text fields are using a dynamic type font style so the table needs to adjust as the text size changes. Don’t forget to set the number of lines property of the text label to zero as it will have a variable number of lines. Also make sure that you do not have an explicit value set for the preferred width of the label (see one more problem below)

Universal Storyboard

The original sample code was an iPhone only project with an iPhone only storyboard. In another move to make it easier to support different sizes of device iOS 8 introduces a universal storyboard. If you look carefully at the screenshot of the new storyboard below you will notice that the views are no longer iPhone or iPad size.

Instead of creating device specific layouts iOS 8 allows you to create a more generic default layout that uses auto layout to adapt to the actual device dimensions. To customise for specific device sizes iOS 8 defines two size classes: regular and compact. The default layout constraints can be overridden in the storyboard for any combination of the size class both horizontally and vertically. I will not go into detail here as the default layout is all we need and gives us a universal app without any extra code.

Setting the Estimated Row Height

Before iOS 8 if you had a table view that had cells with varying row heights you needed to use the table view delegate method tableView:heightForRowAtIndexPath: to return the height of each row. Loading the table view calls this method for every row in the table which for large tables can be slow. iOS 7 introduced tableView:estimatedHeightForRowAtIndexPath: to give an estimate of the row height which improved table load speed but you still need the height of each cell.

In iOS 8 we no longer need either of these methods as we can directly set the estimatedRowHeight in the table view controller and rely on auto layout to do the rest. In the WWDC session they use viewDidLoad for this:

- (void)viewDidLoad
{
   ...
   ...
   self.tableView.estimatedRowHeight = 100.0;
}

Since we no longer need to waste time with tableView:heightForRowAtIndexPath: we can also remove the dummy prototype cell from the table view controller.

Loading a Table View From a Storyboard

Unfortunately if you build and run at this point it does not work. The cell height is incorrect causing a truncated text label.

The root cause of the problem is that when the storyboard loads the table view it sets the rowHeight property to the value from interface builder. To make use of self-sizing cells we need to set estimatedRowHeight and leave rowHeight to what should be the default in iOS 8 - UITableViewAutomaticDimension.

The WWDC session mentions this problem and even that the next seed fixes it. Since that does not seem to be the case yet (in iOS 8 beta 5) we need to fix it in viewDidLoad:

- (void)viewDidLoad
{
   ...
   ...
   self.tableView.estimatedRowHeight = 100.0;
   self.tableView.rowHeight = UITableViewAutomaticDimension;
}

One More Problem

If you build and run now it almost works. Unfortunately the cells displayed on the initial screen are still incorrect. If you scroll the table view you will see that the height is fine for new cells as they appear on screen. I suspect the problem is that the initial cells load before we have a valid row height. The workaround is to force a table reload when the view appears:

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}

Update 18 August 2014: I have logged rdar://17799811 with Apple to see if this is really a bug or something I am doing wrong. There are some further suggestions for workarounds in the comments. I will post an update if this changes in future iOS 8 releases.

Update 10 January 2015 Issue resolved: I received a reply from Apple Engineering that pinpointed the issue. The lineLable UILabel had an explicit contentWidth set as shown below that was causing it not to be sized correctly.

Removing the explicit content width (see below) resolves the issue. The GitHub repository has the updated storyboard.

Wrapping Up

That was a lot of words and not much code so I hope you made it this far. This is one of those cases where SDK improvements saves us code. The combination of universal storyboards, auto layout and self-sizing cells makes it easy to support different device dimensions with almost no code.

The new and improved auto-sizing table view code is in my GitHub repository. The name of the sample project is SelfSize.

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

Unsubscribe at any time.
Archives Categories
comments powered by Disqus