iOS 8: Self Sizing Table View Cells with Dynamic Type

When Dynamic Type came out in iOS7, I was hoping to get to use it. After all, who doesn’t want to make their app more readable for users who need it?

Well, it seemed really difficult to get started with it – I’m still figuring the best way to resize my iOS7 cells with AutoLayout!, so I never “had time” to work on it much, especially with more “important” features taking a priority.

If you’re still curious / want to compare, the best example of how to do Dynamic Type in iOS7 is by far James Dempsey’s WordWithoutFriends example he presented at CocoaConf San Jose I was lucky to attend in April this year.

iOS8 is a completely different story. With self-sizing tableView and collectionView cells, dynamic type is so easy, a cave man can do it. Seriously, though, you really have no excuse NOT to do it moving forward.

To show you how easy it is, I built a simple Seinfeld Quotes app. Here is how it works with Dynamic Type:

Here is all I had to do to make it happen:

Use Self-Sizing Cells

I don’t know about you, but for me, self-sizing tableView and collectionView cells are seriously the most exciting feature of iOS8. No way I’m going back to calculating my cell heights the old way!

To set up your self-sizing cell, just add AutoLayout to your cell, specify your tableView’s estimatedRowHeight, and set the tableView’s rowHeight to UITableViewAutomaticDimension (this will be the default setting in future XCode6 versions).

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.estimatedRowHeight = 89
        tableView.rowHeight = UITableViewAutomaticDimension
    }

That’s it! You should be using AutoLayout anyway, so this is basically replacing all your row height calculations in tableView:heightForRowAtIndexPath: with just 2 lines of code (oh wait, make it 1 line of code in a few XCode6 betas!).

Use preferredFontForTextStyle

Again, using preferredFontForTextStyle is something you should be doing anyway. Instead of thinking of your fonts as hard coded for each screen and scenario, you can use the built-in styles as appropriate:

UIFontTextStyleHeadline
UIFontTextStyleBody
UIFontTextStyleSubheadline
UIFontTextStyleFootnote
UIFontTextStyleCaption1
UIFontTextStyleCaption2

You can also specify your own text styles as your designer defines. Take a look at this example UIFont category for specifying your own text styles.

When assigning the preferredFontForTextStyle font to the labels in your cell, make sure it is assigned somewhere where it is recalculated every time the cell data gets re-calculated.

For example, if you set your font in the awakeFromNib: function override, it’ll be set only when the cell is first displayed on the screen, but not when it is recycled by the tableView as the user scrolls. In one project, I set up my font information in a static struct (along with text information, and font color) – this again will not work, since the font will not be recalculated after it is set the first time.

In my Seinfeld Quotes app. I created a custom QuoteTableViewCell, with a configure function that gets called every time tableView:cellForRowAtIndexPath: is called. This is where I placed my font configuration:

    // QuoteTableViewCell
    func configure(#quote : Quote) {
        
        quoteContentLabel.text = quote.content
        quoteContentLabel.accessibilityLabel = "Quote Content"
        quoteContentLabel.accessibilityValue = quote.content
        
        scenarioLabel.text = "- \(quote.scenario)"
        scenarioLabel.accessibilityLabel = "Quote Scenario"
        scenarioLabel.accessibilityValue = quote.scenario
        
        // font is set here!
        quoteContentLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
        scenarioLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
    }

Remember, it’s dynamic type, not static. So treat your font as if it can be recalculated to a different UIFont value every time!

Reload Data on Content Size Change

UPDATE: As of XCode 6 GM Release, this step is no longer necessary! You can see the most updated sample project on Github here.

If you completed the last two steps – using self-sizing cells and preferredFontForTextStyle for your Font – Dynamic Type will now work if the user hard-quits your app after resetting the text size on her phone.

Of course, you don’t want the user to have to quit your app to get the correct font sizes, so all you have to do is reload your tableView on the UIContentSizeCategoryDidChangeNotification:

 override func viewDidLoad() {
        
        super.viewDidLoad()
        
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "onContentSizeChange:",
            name: UIContentSizeCategoryDidChangeNotification,
            object: nil)
        
        tableView.estimatedRowHeight = 89
        tableView.rowHeight = UITableViewAutomaticDimension

    }
    
    override func viewDidDisappear(animated: Bool)  {
        super.viewDidDisappear(animated)
        
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
    
    func onContentSizeChange(notification: NSNotification) {
        tableView.reloadData()
    }

That’s it! Like I said, there is really no reason NOT to support Dynamic Type. With just a few simple modifications to your app (which are best practice anyway), you can easily make your vision-challenged users A LOT happier.

You can view the full source code example on Github here.

Enjoy the article? Join over 20,000+ Swift developers and enthusiasts who get my weekly updates.