Accessibility is one of those things that everyone knows they should be better at implementing, but falls to the wayside when deadlines are looming. I liked the idea of supporting Dynamic Type in the apps I write, but it was kind of a slog to implement it. There are two main ways to do it right out of the gate with what is built in to iOS, code and Interface Builder.
Let’s go with code first. The easiest way to get the preferred font for a given text style.
iOS will set whatever the “Headline” style is to your
titleLabel’s font. If a user changes their default font size in Settings, it’ll scale with that.
Interface Builder makes it fairly simple too:
But there’s a catch. If a user launches your app, then changes the font size, your app won’t display any new changes until it has quit and is relaunched. You can watch for the
UIContentSizeCategoryDidChangeNotification notification though and then re-render your text based views then1. So, regardless of your choice you’ll have to code out a lot of labels, buttons, and text fields. In a normal app, that’s hundreds of lines of boring code.
The other big downside is that you’re limited to the system font. So, San Francisco on iOS 9 and Helvetica Neue on older versions. I like those fonts just fine, but sometimes you need to switch it up.
I first came across Big Nerd Ranch’s BNRDynamicTextManager library and found it to be pretty useful. You still have to wire up each field (or use subclasses), and you’re still stuck with the system font, but it handled all the notification watching for you. Here’s a sample:
The library was lightweight and easy to understand2, which got me thinking.
I decided to port BNRDynamicTextManager to Swift and see if I could add some functionality to it. Porting it wasn’t a big deal, although I made a couple of changes to better match Swift’s idioms. I then added a couple of nice features on top.
Big Feature 1 — Custom Fonts
Watcher methods can take the name of a custom font:
1 2 3
This will apply the font size of the “Headline” style to your label, but keep the glorious MarkerFelt font. If the
fontName is not associated with an installed font, it will fall back to the system font.
Big Feature 2 — Custom Styles
iOS 7 brought us six text styles, and iOS 9 brought four more3. They cover a decent range of use cases, but sometimes you still want your own. You can do that with the
1 2 3 4
In order to keep with the feel of how Dynamic Type works, you pick a style to serve as a base reference for the new one and then scale it from there. That way you don’t hardcode a point size. You can then watch it the same way you would before:
1 2 3
Big Feature 3 — Global Styles
I liked how it was working now, but it was still a long process to cover all the text based views that could be in an app. I wanted a solution that would make it super easy to handle the 90% scenario. Enter the
StyleConfig constructs. The
StyleWatcher will recursively examine each subview in a parent view and watch them with the
DynamicTypeManager if they have a text style associated with them. That way you can set up the text-styles solely UI within Interface Builder and remove an entire swath of code. Additionally, you can set a global style that the watcher will use to find your custom font (or style).
1 2 3 4 5
Once that has been set up, tell the watcher to watch a view:
1 2 3 4 5 6 7
And the magic will happen.
You can customize that process too. The
watchViews method can take its own
StyleConfig as well, allowing you to define multiple different styles and applying them where necessary.
I’m pretty happy with the way Gliphy turned out. The code is pretty straightforward too; no mind bending swiftisms required. There are some downsides though. Attributed strings aren’t handled at all, and due to their nature, they probably will never be. Gliphy only watches four kinds of views: labels, buttons, textfields, and textviews, (plus their subclasses) and is a little difficult to extend beyond that.
All said though, it should make supporting Dynamic Type something more than an afterthought in your apps.
There is a bug in the iOS simulator and Xcode 7.3. You can change the font size, but the notification will not fire. It still works on a device though.
I raise my glass to jgallagher for that.
Here they are: