Practical Dynamic Type, Part 3: Attributed Text
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
adjustsFontForContentSizeCategory
.But, on iOS 10, an AttributedString
in an UILabel
does not respond to adjustsFontForContentSizeCategory
.
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:
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:
Unit Testing
Using this technique, it is possible to unit test the Dynamic Type pointSize
as well!
Which, as expected, passes!
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.