Keehun Nam

Senior Systems Engineer at Cloudflare

Practical Dynamic Type Part 3: Attributed Strings

Ensuring that applications remain accessible for all is one of our highest priorities at Livefront, and we believe that a key component in achieving that goal is to flawlessly support Dynamic Type in all of our applications. Part 1 of our Practical Dynamic Type series focused on supporting iOS 10 and Part 2 focused on unit testing. This third installment will focus on Attributed Strings.

You can access the complete demo app for this article, here.

You may be asking: “Isn’t there already adjustsFontForContentSizeCategory?” You’re right! Setting aside for a moment the fact that it is not unit testable (as discussed in Part 2), let’s see what happens when we assign an Attributed String to an UILabel with adjustsFontForContentSizeCategory = true.

On iOS 11 and above, everything works as expected

A gif demonstrating that Attributed Strings resize properly on iOS 12.
UILabel on iOS 11 correctly responds to adjustsFontForContentSizeCategory.

But, on iOS 10, an AttributedString in an UILabel does not respond to adjustsFontForContentSizeCategory.

A gif demonstrating that Attributed Strings does not resize properly on iOS 10.
UILabel on iOS 10 does not respond to adjustsFontForContentSizeCategory due to the lack of UIFontMetrics on iOS 10.

Let’s fix this

It’s clear from attempting to use adjustsFontForContentSizeCategory on iOS 10 that we will have to manually handle updating the font size, just as we’ve done in Part 1 and Part 2.

First, let’s define a UIView that hosts a UILabel displaying multiple attributed strings:

(Note that you will need to pull the FontMetrics class from Part 2.)

Second, let’s hook into FontMetric’s notification FontMetricsContentSizeCategoryDidChange:

What’s next?

Whereas we’ve been able to set the font size property on the entire UILabel inside the uiContentSizeCategoryChanged() before, now we will have to set the type size on each individual formatted segment within the AttributedString as each formatting segment may have different type size. Therefore, in response to ContentSizeCategoryDidChange, we will have to enumerate through each formatting segment of the overall AttributedString and update each formatted segments' type sizes.

Let’s first enumerate over the AttributeString to access each of the four sub-attributed strings we created above:

In order to calculate the new type sizes, we will need to have separate FontSetter closures that the enumeration block in uiContentSizeCategoryChanged() can use on each attributed substring (see lines 11, 20, 31, and 40 below). This allows the individual attributed substring to get its type size regenerated with its own FontSetter.

And now, all we have to do is get a new UIFont with updated sizes by re-executing the FontSetter closure for each substring and then assigning those new fonts to the preexisting string:

A gif demonstrating that Attributed Strings now resize properly on iOS 10.
UILabel on iOS 10 now responds to Dynamic Type.

Unit Testing

Using this technique, it is possible to unit test the Dynamic Type pointSize as well!

Which, as expected, passes!

img

Now, you can support Dynamic Type with AttributedString on pre-iOS 11 devices, and unit test it, too! 🍰

You can access the complete demo app for this article here. (Please note that Neutraface Slab Text font shown throughout the article is not included, and it has been replaced with Courier.)

Keehun finds ways to unit test everything at Livefront.

Thanks to Collin Flynn , Chris Sessions , Sean Berry , and Mike Bollinger for their editorial feedback.