Nowadays just about everyone, everywhere, regardless of age and ability, uses a smartphone.
With that in mind, it’s more important than ever to consider all the different ways to make your app accessible to everyone to ensure an inclusive experience. In this series, we’ll be discussing different ways iOS developers can make their apps more accessible.
First up, we’ll be talking about Dynamic Type.
Dynamic Type is a feature on iOS that enables the app’s content to scale based on the user's preferred content size. It helps users who need larger text for better readability. And it also accommodates those who can read smaller text, allowing for more information to appear on the screen. In this blog post I’ll be covering the following topics.
- Intro to Dynamic Type
- The basic setup
- Dynamic Type with custom fonts
- UI tips
Intro to Dynamic Type
Let’s first start with some information about Dynamic Type. Dynamic Type is a feature that enables the app’s content to scale based on the user’s preferred content size. The preferred content size can be found in Settings -> Accessibility -> Display & Text Size -> Larger Text. If you have never changed this setting before, you’ll see a screen that looks like the image on the left. If you want to use the Larger Accessibility Sizes, turn on the “Larger Accessibility Sizes” toggle in the upper right.
Once we flip the toggle, there are five more options, which are the Accessibility options.
When Larger Accessibility Sizes is turned off, the sizes from left to right are: xSmall, Small, Medium, Large (Default), xLarge, xxLarge, xxxLarge
When Larger Accessibility Sizes is turned on, the sizes from left to right are:
xSmall, Small, Medium, Large (Default), xLarge, xxLarge, xxxLarge, AX1, AX2, AX3, AX4, AX5.
Basic setup for Dynamic Type
If you’re using the iOS built-in text styles, you get support for Dynamic Type out of the box. We’ll go into more detail about these built-in text styles later, as they will come into play a little more with supporting custom fonts. For now let’s jump into the most basic implementation.
When building your views programmatically, use:
label.font = .preferredFont(forTextStyle: .body)
Yep it’s that simple! However, one thing you don’t get out of the box is the text reacting to the user changing their preferred content size while your app is open. In order for your text to react to those changes, you have to set adjustsFontForContentSizeCategory to true.
label.adjustsFontForContentSizeCategory = true
When building your views in Storyboard:
The setup is just as easy when using Storyboards, it’s only two steps.
- Choose your built-in text style.
2. Check the box “Automatically Adjusts Font” for your text to react to any preferred content size changes.
That wraps up the basic setup of supporting Dynamic Type in your app. However, in most apps, you won’t be using the built-in text styles, you’ll be using a custom font. In the next section we’ll go over how to support Dynamic Type using custom fonts.
Dynamic Type using custom fonts
The first thing we need to learn about when supporting Dynamic Type with custom fonts is Apple’s utility class UIFontMetrics. This came out in iOS 11 and is used to scale custom fonts. You create instances of this class by passing in one of the built-in text styles we went over in the first section. This part threw me off a little at first. I asked myself, What does it matter what text style we use if we’re using a custom font? It matters because these text styles scale differently from one another. The following spreadsheet shows the different font sizes for each text style at each content size category. In the far right column you can see how much each text style scales from the Default (Large) preference to the largest option (AX5).
With that in mind, you can see that it’s important to choose a text style that has a Default size close to the font size you’re scaling. For example, if you use the Body text style to scale a label that has a font size of say 28, it’s going to end up scaling much too large. Instead, using the Title 1 text style would be more appropriate as it’s Default size is 28.
Okay now that we’ve got that out of the way, let’s jump into the code for supporting dynamic type on UILabel.
If let customFont = UIFont(name: "Noteworthy", size: 17) {
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
}
You can also use the `default` class variable on UIFontMetrics which uses the Body text style.
if let customFont = UIFont(name: "Noteworthy", size: 17) {
label.font = UIFontMetrics.default.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
}
Pretty simple, in just a few lines of code your label now supports Dynamic Type and will adjust to any content size preference changes. There is one issue with this. If you have a screen with many labels, this code can add up quickly. There are ways that it can be isolated and reused, and, if you’re building your views programmatically, this is a non-issue. However, if you’re using Storyboards, there isn’t a straightforward way to support Dynamic Type in Interface Builder and it’s not ideal to have to create IBOutlets for every label to add support for Dynamic Type. Over time this was a big enough pain point for our team that we decided to create a framework for supporting Dynamic Type right from Interface Builder. I’ll be covering this framework on my next blog post.
UI tips
Lastly, I want to leave you with a few small tips on how to be proactive when building your views to account for font scaling.
1) Use scroll views early and often. Pretty much every screen will have enough content to where it will need to scroll when the text is set to an accessibility size. Even if there isn’t enough content at first, that could change in the future. And adding a scroll view after the fact can be painful.
2) Configure table view cells and collection view cells to be self sizing to account for text size changes.
3) In situations where you have views that are horizontally aligned, use stack views. With stack views, if the preferred content size changes to an accessibility size, you can change the axis to vertical to allow for more space. You can listen for changes to the preferred content size by overriding func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) in your view controller. This blog post gives a good example of how to accomplish this.
In conclusion, we learned about supporting Dynamic Type programmatically and in Interface Builder, for both system fonts and custom fonts. Although it would be nice to have a cleaner way to support Dynamic Type in IB when using custom fonts, it’s still relatively simple and goes a long way for the end user.
That’s it for now, be sure to check out the upcoming posts in our accessibility series!