iOS Dynamic Type with custom fonts
iOS offers a bunch of built-in Accessibility features, such as Voice Over, Switch Control, and Dynamic Type. The last one allows users to define preferred content size, therefore it can provide a much more consistent reading experience for everyone.
“…iOS provides an extraordinary opportunity to deliver a superior mobile experience to every customer, including those with special needs.” – from Apple’s Accessibility on iOS
As we are going to see in this post it’s simple to use its API, but it gets repetitive when programmatically configuring views. Let’s see how we can make it more readable and easier to maintain when using it with custom fonts.
Text styles
Every app has a label, right? Let’s say we want our label’s text size to be dynamic.
iOS leverages typography with great focus on legibility. It comes with built-in text styles that can be used to display different text content and that lets the system know how best to adjust its size. Whenever we want views to adapt its context size we use text styles. You can configure text styles in either Interface Builder or your source code.
Auto updating views
Pretty simple, isn’t it? Ok, so right now the label has a system font and only adapts itself when the user starts the app. That’s not so good at all, so let’s see how we make our views to adapt to user content category settings changes.
Since iOS 10 with the addition of UIContentSizeCategoryAdjusting protocol that became easier. All base UIKit classes that have text UILabel, UITextField, and UITextView conforms to it. In this case, having adjustsFontForContentSizeCategory set to true takes the job of resizing.
Under iOS 10
When working with important products every user counts. In this case to listen to content category changes of those who still uses iOS 9.*, generally around 3%, UIContentSizeCategory.didChangeNotification enters the game.
Custom fonts
That’s pretty nice! Our label now adapts its text size whenever the user changes the preferred settings. Nevertheless, the awesome apps that we make generally a design system to follow and custom fonts, which since iOS 11 are supported through UIFontMetric class. To use it we initialise a UIFontMetrics object injecting a text style, then we take care of scaling the font by calling its scaledFont function.
The above code works 👍, but doing it for all our fonts is quite repetitive and it can get a bit messy. Imagine how much pain would it be to also set the views to adapt to content size, configure the fonts, and all other configuring boilerplate. So, considering that you already know how to bundle custom fonts, let’s see how we can make use of some nice Swift features to use them along with Dynamic Type for all views on easy.
So let’s say our app shows famous movies and they have their own displayable fonts. First, we can define an enum for each of them.
Then we create a CustomFont protocol with conditional conformance where Self is RawRepresentable and Self’s RawValue has the type String, so we are able to get a constructed custom font with the name represented by its rawValue and the given size.
Now we link our jurassic park custom font to our CustomFont protocol, and add it as a child of UIFont extension so it gets more clear that we are working with UIFonts.
Custom fonts can be set as below.
Scalable fonts
Now let’s see how we can make our custom fonts to scale. Here we create a ScalableFont protocol where we define what we expect from scalable font objects. After that we extend UIFont to allow us to scale any font that we work with.
At this moment we are able to scale our declared custom fonts, defining or not a text style and a max size.
Awesome! Yes, but right now we don’t really know if any of our custom fonts are really there (until we run the old print UIFont.familyNames to see the available fonts).
Conditional conformance, again…
I personally love conditional conformance and how Swift allows us to constraint a protocol extension to defined types. We are gonna make use of it creating an Optional extension where Wrapped is UIFont, so we can precondition failure whenever unwrapping a UIFont fails.
Now we can improve our CustomFont behaviour to retrieve only unwrapped fonts or pre condition failing when the font is not well bundled.
Right now, for each view, we would have set the font, make them update to content size, and all other kind of boilerplate when programatically creating them. That’s why we are going to use factories, static in this case.
Static factories
The factory pattern is a creational pattern which can be used to deal with the complexities of creation and configuring objects. To no more you should definitely read, John Sundell’s post.
To finish let’s make also it easier to configure our labels or any other views that should adapt its content size, by using static factories, that could be global or available for specific domains, depending on the use case.
When programmatically creating views, that’s end up being really great, in the other hand for @IBOulets we would need to handle already initialised objects.
That’s the final usage
Conclusion
Supporting Dynamic text size is simple, but dealing with custom fonts, and views configuration along with it, can get a bit disorganised. Handling it with static factories, conditional conformance and enums might help to set an easy to read an maintain code, that makes our app visually accessible for our users and nice to work for our team.
To make apps fully accessible, I recommend you to watch the 2017 WWDC’s Building Apps with Dynamic Type session. There’s also a lot of other great posts covering everything about Dynamic Type in our great community.
That’s it for today. This is my very first blog post and I would really love to receive comments, feedbacks and suggestions. Also if you have any doubt, feel free to contact me or hit me on my Twitter account - @_marinofelipe.
Update - April 08, 2019
Sample project available on GitHub :)