Dynamic Type: Scaling Custom Fonts
Hello, I’m Kevin, iOS developer at Immoweb. This article will be one of a series about supporting Dynamic Type.
But before reading further, you might ask…
What is Dynamic Type? Why should I support it in my app?
Let’s first answer those questions!
If you know the answers already, you can skip them.
What is Dynamic Type?
Dynamic Type is a feature provided by Apple.
It allows you to scale your app’s interface (texts and images) and adapt your layouts based on the user’s preference.
A user can define a preferred text size to use across the system in Settings → Accessibility → Display & Text Size → Larger Text.
iOS ecosystem and every app should adapt according to the chosen size.
Why supporting Dynamic Type?
Because it’s the right thing to do, period. There’s even a saying: “Do what is right, not what is easy.”
Need some other good reason? Well, it’s a win-win. It will improve the user experience of your app, and you will benefit from it, because it impacts a lot of users.
Some statistics
The World Health Organization reported that about 15% of worldwide population lives with some form of disability.
If we look at statistics from specific countries, we get 26% in the United States and 18% in France.
But this gathers all types of disabilities. What about visual disabilities? How many people are using the Dynamic Type functionality in iOS?
In the Immoweb iOS app, it has been over a year since we started recording statistics about which accessibility features are used by our users.
Immoweb is one of the biggest Belgian apps (and is mostly used in Belgium)
If we look specifically at users using the Dynamic Type functionality, we can clearly see it gathers 29% of our audience. We need to make their experience with the app a great one.
Adapting to bigger sizes, for accessibility, allows us also support people who wants smaller sizes (and there are almost as many).
First approach
If you are using custom fonts in your app and want them to scale properly, this is the basic approach:
- You use
UIFontMetrics.default.scaledFont(for:)
method to get a version of your font scaled to the current Dynamic Type size. - You set
adjustsFontForContentSizeCategory
totrue
to allow the font to update when the size category changes.
Support for iOS 10 and earlier
For those who still need to support iOS 10 and earlier version, UIFontMetrics
is not available. I redirect you to this solution.
Supporting a custom design system
In our app, we use different text styles which could be mapped toUIFont.TextStyle
s.
But as it often happens, in our design system, we are using slightly different styles and we don’t want to constraint ourselves to Apple text styles.
Here’s a comparison of Apple text styles and those from Immoweb design system:
You can see we are using more styles than Apple and depending on the style, we use one of two fonts: Montserrat or PT Sans.
Use our custom text styles
So we decided to go with our own custom text styles:
So far:
- We have created a
TextStyle
struct that will be used to define each of our text styles. - We created an init defaulting to no emphasis and using our main font, PT Sans.
- We created an
Emphasis
enum to specify if the text style should be in bold, italic or stay regular. We chose to not use directlyUIFontDescriptor.SymbolicTraits
and all its values because we don’t need them (so far). So we created this enum with only the cases we need, to be more clean.
Defining our text styles
Now we can add our styles like so:
We decided to make TextStyle
a struct and not an enum so we can easily extend it to create new text styles if needed. And it’s usually the case. You’ll end up with a few exceptions for certain specific UI’s.
Now let’s see how we’ll use those styles we’ve just created…
Extend UIFont
We are going to create fonts with our new TextStyle
by extending UIFont
class with 2 static functions:
Here we use the UIFontDescription
class to be able to specify symbolicTraits
.
We can now use this to get the font from one of our TextStyle
:
It is clean 👌
We can even call UIFont.font(style: .h1)
if we need a fixed sized font (like for tab bar and navigation bar texts).
Now we can see that there’s still something we could improve:
Each time we use label.font = UIFont.scaledFont(style: …)
we have to also add the statement label.adjustsFontForContentSizeCategory = true
which is a bit redundant.
What we would do, ideally, is to have an API that looks like this:
label.apply(textStyle: …)
And it would, by default, use the font scaled to the current content size category and adapt to content size category changes.
Let’s see how we can implement that…
Create a common protocol
We want to add a apply(textStyle:)
method to UILabel
, UITextField
, UITextView
and UIButton
. For that we’ll create a common protocol. Let’s call it TextStyleAdjustable
:
Our protocol inherits from UIContentSizeCategoryAdjusting
so we can have access to the variable adjustsFontForContentSizeCategory
.
Now we can implement this protocol:
Note that UILabel
, UITextField
, UITextView
already implement UIContentSizeCategoryAdjusting
protocol so we don’t need to implement adjustsFontForContentSizeCategory
variable for them, but we do for UIButton
.
We can use our new API:
And all texts in our app will be correctly sized and adapt automatically, all of that with custom fonts 🙌
Add a SwiftLint check
If you are using SwiftLint I recommend adding a custom rule to make sure we don’t use unwanted API’s by mistake, such as:
UIFont.preferredFont(forTextStyle:)
UIFont.systemFont(ofSize:)
UIFont.boldSystemFont(ofSize:)
- etc.
This way we’ll avoid using the wrong APIs by mistake. You can change severity: error
to warning
to start if needed.
You can still disable the rule in specific cases by adding //swiftlint:disable:next use_scaled_fonts
on the line before the call to one of the prohibited API’s we listed above. An example of a case we would do that is when setting a font for the tab bar, which should never scale.
Final code
You can have the final code here.
Conclusion
We saw that the Dynamic Type feature is used by many users (29% of Immoweb iOS audience) and it is important to support it in our apps.
As it is usually the case, our design systems include more text styles than Apple is offering by default with UIFont.TextStyle
, for this reason we created our own TextStyle
struct to support them.
We want scalable texts to be the default behaviour in our apps, so we created API’s to enforce that: UIFont.scaledFont(style:)
and <textField/textView/label/button>.apply(textStyle:)
.
To go further, in the next articles we’ll see how we can update our layout, scale our images dynamically to the Dynamic Type and more…
I hope you liked this article!
See you soon 👋
Resources
- Scaling Fonts Automatically — Apple Developer Documentation
- Building Apps with Dynamic Type — WWDC Video
- Dynamic Type Sizes — Human Interface Guidelines
- Adding a Custom Font to Your App — Apple Developer Documentation
Other articles of the “Dynamic Type” series
Special thanks to Vincent Martin for proofreading this article 🙏