Supporting Dynamic Type with Custom Fonts on iOS

19 October 2019

One way in which designers look to imprint branding on an app is to use a custom typeface throughout the user interface. Whilst this can give an app a distinctive look, it often comes at the expense of accessibility. This is because a key element of iOS's support for visually impaired users is the Dynamic Type system, which allows users to increase or decrease the size of text rendered across the system. Whilst it is easy for apps to support Dynamic Type when using the built-in system typeface, doing so with a custom font requires additional dev effort which is all to often overlooked.

Making Dynamic Support with Custom Fonts Possible

iOS 11 introduced a new API - UIFontMetrics - which allowed custom fonts to be scaled in the same way as the system fonts in response to the user's Dynamic Type system settings. However, the API for doing so is fairly cumbersome, as Apple's same code demonstrates:

guard let customFont = UIFont(name: "CustomFont-Light", size: UIFont.labelFontSize) else {
    fatalError("""
        Failed to load the "CustomFont-Light" font.
        Make sure the font file is included in the project and the font name is spelled correctly.
        """
    )
}
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: customFont)

Creating a custom font instance and passing it to UIFontMetrics every time text needs to be displayed is a far cry from the ease of use of the UIFont API that allows developers to easily make use of the system typeface with Dynamic Type text styles:

label.font = .preferredFont(forTextStyle: .body)

If only there were a way to make an application's custom font as easy to use with Dynamic Type as the system font APIs.

Making Dynamic Support with Custom Fonts Really Easy

Fortunately there is an alternative which provides exactly this - an API just as simple to use as the system APIs, but for your app's custom font. This alternative is the Ampersand framework, and it gives us the following API for our custom fonts:

label.font = .applicationFont(forTextStyle: .body)

Rather nice, I'm sure you'll agree. But how can this be so? All we need is 3 simple steps:

Step 1 - Integrate the Ampersand Framework in to Your App

Ampersand can be integrated as a Swift Package in Xcode using "File > Swift Packages > Add Package Dependency..." and entering the Ampersand repository URL (https://github.com/darjeelingsteve/Ampersand).

Step 2 - Configure the Application Font

In order for our application font to work with all of the Dynamic Type text styles, we must specify which font from our typeface corresponds with each of the system's text styles, along with the point size that should be used at the default system type size setting. We also need to specify which of our typeface's fonts correspond which each of the system font weights, so that we can create instances of UIFont for our custom typeface with a specified point size and weight when required.

We do this using a JSON configuration file, a partial example of which looks like this:

{
  "styles": [
    {
      "textStyle": "UICTFontTextStyleTitle0",
      "fontName": "Avenir-Medium",
      "pointSize": 30
    },
    {
      "textStyle": "UICTFontTextStyleTitle1",
      "fontName": "Avenir-Medium",
      "pointSize": 25
    },
    ...
  ],
  "weights": [
    {
      "fontWeight": "ultraLight",
      "fontName": "Avenir-Light"
    },
    {
      "fontWeight": "thin",
      "fontName": "Avenir-Light"
    },
    ...
  ]
}

A full example of a configuration file for the Avenir typeface can be found here.

Step 3 - Register the Application Font

Once we have created our JSON configuration file, we need to include it as a resource in our app. This allows us to create a URL to the file which we can then pass to the application font registration function:

let configurationURL = Bundle.main.url(forResource: "Avenir", withExtension: "json")!
UIFont.registerApplicationFont(withConfigurationAt: configurationURL)

This simple one line call to +[UIFont registerApplicationFont​WithConfigurationAtURL:] is all we need to setup our application font.

All that's left to do now is make use of the simple set of application font APIs that Ampersand supplies as UIFont extensions. If we would like an instance of our application font for the headline text style, it's as simple as this:

let headlineFont = UIFont.applicationFont(forTextStyle: .headline)

If we would like an application font at a specific point size and weight, that's simple enough too:

let semibold = UIFont.applicationFont(ofSize: 17, weight: .semibold)

Conclusion

Supporting Dynamic Type with your app's custom font is now a piece of cake thanks to the Ampersand framework. If you haven't already, head over to the GitHub repo to check out the full details, or take a look at the sample project to see just how easy it is to integrate Ampersand and get fantastic accessibility support in your own apps with custom fonts.