Supporting Dynamic Type at Airbnb

An overview of process, tips, and pitfalls we learned while bringing Dynamic Type to Airbnb. For you to reference when supporting Dynamic Type in your apps too!

Noah Martin
Jul 24, 2019 · 5 min read

By Noah Martin & Hugo Ahlberg

Image for post
Image for post

Background

Since iOS 7, Dynamic Type has allowed users to choose a prefered font size for their phone. At Airbnb, we try to build an app that our entire community can use — since Dynamic Type is a critical accessibility feature, we knew supporting it would make more people able to effectively use our app, some of them probably for the first time. To validate the importance of this feature, we examined the data and saw as much as 30% of people using our app had a preferred font size that was not the default. This usage is not skewed towards particular sizes, but evenly spread across larger and smaller than default.

It turns out, supporting this preference creates a consistent experience across the OS that users will notice. Experimenting with Dynamic Type on individual features in the Airbnb app resulted in a significant increase in engagement, helping move our bottom line metrics. If you spend the time to support Dynamic Type in your app too, users will surely thank you for it!

Image for post
Image for post
Font size selection in iOS

Why is it important?

Going beyond the metrics, supporting Dynamic Type holds your UI components to a higher level of quality. Layout will need to be robust enough to handle a wide range of preferences, similar to variations created by localization and device screen size. Since much of our development time is spent on single devices and languages, bugs only reproducible in certain configurations will too often slip through. Fortunately, many of these are now being caught during Dynamic Type testing. If you already support varying screen sizes through UITraitCollection and translations with various length strings, there’s a good chance you’ve done most of the work to support Dynamic Type.

Design

The majority of bugs we encountered when large font sizes were used had to do with text not fitting their containers. To resolve these, we created a few design recommendations. First, widths and heights became flexible, allowing text to expand to multiple lines. In many cases this should have already been done since some languages can be much longer than the English words we include in design mocks.

Image for post
Image for post

Second, we had to make sure fonts scale the correct amount. This is done by assigning every text a corresponding UIFont.TextStyle. Using a larger TextStyle indicates your font is already big, so it doesn’t need to increase in size as much.

Third, we had to fix some labels that were changing size even though they shouldn’t be eligible to scale. Our recommendation is everything part of the scrollable area on the screen should scale, and everything else should be left static. However, anyone with large Dynamic Type enabled still needs a way to view text in smaller containers such as tab bars. If you use all standard UIKit elements this is handled by the Large Content Viewer:

Image for post
Image for post

We filed a bug report with Apple, requesting a new API for presenting these popups from custom views. This capability has since been included in the iOS 13 beta, so you’ll be able to see it in the Airbnb app soon.

Engineering

iOS provides mostly automatic Dynamic Type APIs for system fonts, but the Airbnb app uses a custom typeface, Cereal. To support Dynamic Type, we rely on UIFontMetrics. This class handles scaling our font size, line height, and tracking.

Each of these attributes exist in two forms:

  1. The unscaled units which are shown to users with the default font size, and are the values we set when creating font attributes.
  2. The scaled units which fit the preferredContentSizeCategory and are what we read at runtime.

Internally, features will request a font using attributes expressed as unscaled units, and will receive an object containing various functions we use to display text which always returns values in scaled units. This ensures any calculation, such as bounding box of text, will use the scaled units. Some of the most common bugs we saw were caused by using unscaled units for layout calculations instead of scaled units.

There are two ways to use UIFontMetrics to convert from unscaled to scaled units.

Method #1:

func scaledFont(for font: UIFont, compatibleWith traitCollection: UITraitCollection?) -> UIFont

Method #2:

func scaledValue(for value: CGFloat, compatibleWith traitCollection: UITraitCollection?) -> CGFloat

There are subtle differences in these approaches. Consider the following examples, each using a different UIFontMetrics method:

Depending on the device you run on, we observed results like this:

Scaled pointSize and lineHeight vary between method 1 and method 2
Scaled pointSize and lineHeight vary between method 1 and method 2

The results aren’t quite consistent, but since we customize line height with NSParagraphStyle we need to use the CGFloat scale function. The UIFont with unscaled point size is scaled directly to get an adjusted UIFont. Here’s a full example to scale an NSAttributedString:

The last step to fully supporting Dynamic Type is to encourage validation across all features, including ones in development. We know developer time is limited, so we automated support for Dynamic Type as much as possible. Happo, the tool we use for UI regression detection, already snapshots existing components. We added an additional step to render with the accessibilityExtraExtraExtraLarge size.

There are no APIs available to programmatically change the simulator’s Dynamic Type settings, so avoid using UIApplication.shared.preferredContentSize. A more testable approach is to query the trait collection of a UIView. In the UIWindow for our Happo Tests, the traitCollection is configured to include a custom content size. The end result is snapshots like these:

Image for post
Image for post
Default (large) preferred content size
Image for post
Image for post
AccessibilityExtraExtraExtraLarge preferredContentSize

With snapshots generated on every code change to the iOS app, developers have a hassle-free way to know new features support Dynamic Type, and easily detect regressions.

Thanks to Amie Kweon, Dylan Harris, Bryn Bodayle, Tyler Hedrick, and Kieraj Mumick for their support on this project!

To try out these features for yourself, download the Airbnb app on the App Store. If you’re passionate about projects like this and iOS development, we encourage you to apply for the roles we have open!

Airbnb Engineering & Data Science

Creative engineers and data scientists building a world…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store