Practical Dynamic Type

One of my favorite WWDC talks this year was Building Apps with Dynamic Type. It presents a number of really nice improvements in iOS 11 for building accessible apps for low vision users.

In particular, the talk features a demo with custom fonts that scale dynamically with adjustments to the iOS system text size. Previously this was only possible by observing UIContentSizeCategoryDidChange and manually re-rendering views whenever something changed.

Having dealt with this problem recently, I imagined tearing out obsolete observers, and replacing hacky workarounds with something simple and elegant. Sadly, I faced a couple obstacles to the golden path presented in the talk.

The first is that I’ll need to support iOS 10 for a good while longer, and it only provides half of what’s required for dynamically scaling custom fonts. The two essential ingredients are:

  1. adjustsFontForContentSizeCategory, available as of iOS 10, and
  2. UIFontMetrics, only available in iOS 11.

The second obstacle is a little more nuanced. After looking at Apple’s Human Interface Guidelines on Typography I asked our design team whether it’d make sense to use Apple’s Dynamic Type styles as a foundation for their typography specs.

As it turns out, they have a number of reasons for bypassing Apple’s standard. In some cases, clients want a more extensive range of styles. More often, we’re building apps for both iOS and Android where the designers must strike balance between the iOS look and feel, and Android’s Material Design Spec. Very rarely would the Dynamic Type styles mesh with what our designers are trying to accomplish.

Well then… now what?

Okay it might not be practical to do things exactly as presented in the forward-looking WWDC talk. Then what, if anything, can we take from it and begin to use right away?

I set about chipping away at my dynamically scaling custom font problem. For apps that support iOS 10, we have adjustsFontForContentSizeCategory, but not UIFontMetrics. A wrapper for the latter seems like a good first step.

A UIFontMetrics wrapper

Apple describes UIFontMetrics as “a utility object for obtaining custom fonts that scale to support Dynamic Type.” I’d previously written something approximating this functionality for an iOS 9 project, and that provided the fallback code I needed for anyone not yet running iOS 11.

FontMetrics wraps a subset of UIFontMetrics. Pass in a font or a value, and get back a copy that is scaled up or down based on the device’s current UIContentSizeCategory.

What’s the difference between UIFontMetrics scaling and calculating it manually? Fonts that have been scaled using UIFontMetrics don’t require any further work to scale up and down when a device’s Dynamic Type settings change. In iOS 10 and earlier, text displays at the correct scaled size when it first appears in a view — but you need to observe and manually redraw the view if the UIContentSizeCategory changes.

Whether it’s critical to scale text dynamically depends. Adjusting the device text size and watching how an app responds is probably more of a software developer behavior than a real world one. But if you want this functionality to work correctly for your iOS 10 users, you’ll still need to include UIContentSizeCategoryDidChange observers for now.

Some helpful factories

With UIFontMetrics taken care of, what comes next? The other piece that’s needed for dynamic custom font scaling is adjustsFontForContentSizeCategory. I’d rather not define this all over the place, and if I did it’s probably something I’d frequently forget. We’ll want some little UILabel factories.

First, however, let’s write some UIFont factories, that ensure we use FontMetrics.scaledFont(for:) by default when grabbing a font.

Easy enough. We can use the text point size specified in our designs, and by default they’ll appear scaled up or down based on the device settings.

Now on to our UILabel factories:

On a little bit of a side note, I’ve recently taken the Nibless Challenge, and so far I love it. Besides eliminating the general nib weirdness from my projects, it seems to be helping with accessible UI. Perhaps it’s because constraints are more rare and deliberate now, and aren’t lost in an ocean of XML.

The UILabel factories above, and similar helpers, allow rapid UI building that could look something like this:

Summary

With the bits of code above, we can begin leveraging iOS 11’s Dynamic Type improvements now, even while continuing support for earlier iOS versions. We have the building blocks to offer full accessibility to low vision iOS users, and it now takes minimal additional effort on our part.

Further Resources

Building Apps with Dynamic Type from Apple WWDC 2017.

Using A Custom Font With Dynamic Type and Auto Adjusting Fonts for Dynamic Type from Use Your Loaf.