Table of contents :
- Default dynamic type text styles
- Use Accessbility Inspector to speed up testing different text size
- Scaling custom fonts
- Adapting label to larger font size
- Detecting text size changes
- Content Size Category (List of different text sizes)
- Adjusting other UI elements on text size changes
- Further Reading
Apple introduced dynamic type in iOS 7 to allow users to specify their preferred text size in the Settings app. App that supports dynamic type will be able to adjust text size based on the user preferred text size.
Settings app > General > Accessibility > Larger Text.
This accessibility feature help users with visual impairment , or weakend vision due to aging to be able to read text on the device. By default, the text size slider is positioned in the center.
According to a report by PDF Viewer app team, around 27 percent of their users have specified a non-default text size, which is quite significant! If your app doesn't support dynamic type, there might be a quarter of users who might be struggling to use your app due to vision problems!
In this tutorial, we will look into how to start supporting dynamic type.
Want to try out Dynamic Type yourself?
Get sample Xcode project containing example used in this post (preset text styles, dynamic sized image), try it out!
https://github.com/fluffyes/dynamicFont/archive/master.zip
Default dynamic type text styles
One of the easiest way to support dynamic type is to use the preset text styles, simply change your label font to the preset text styles and you will get dynamic type support for free .
There are a variety of text styles ranging from the smallest Caption 2 to the largest Large Title to suit your layout.
Here is how they look on the default text size settings :
And in larger text size settings :
By default, the preset text styles font size are set when the app launches, meaning if the user change the settings font size in the mid of using your app, they need to quit and relaunch your app to see the updated text size.
To support auto text size adjustment (ie. the app text size changes automatically when user adjust the text size in Settings app, without having to relaunch your app), simply check the "Automatically Adjusts Font" checkbox in the Attribute inspector.
And the font size will auto adjust with the Settings text size without needing to relaunch :
Not a fan of using Storyboard / Interface builder? To support dynamic type programmatically, we can use the preferredFont(forTextStyle:) method of UIFont.
// set the font text style to large title
label.font = UIFont.preferredFont(forTextStyle: .largeTitle)
To support auto adjustment for text size changes (same as the "Automatically Adjusts Font" checkbox), set the adjustsFontForContentSizeCategory to true.
// set the font to auto adjust to text size changes in Settings app
label.adjustsFontForContentSizeCategory = true
Use Accessbility Inspector to speed up testing different text size
It can be quite tedious to keep switching between Settings app and your app to adjust for different text size and test. To speed up this process, we can use the Accessbility Inspector in the Xcode developer tools section.
In the accessibility inspector, select the simulator / device you are using, then click the settings icon, and you can adjust the text size easily by dragging the font size slider (default size is 4th tick from left).
Remember to check the "Automatically Adjusts Font" checkbox or set adjustsFontForContentSizeCategory to true for this to work.
Scaling custom fonts
As the preset text styles use System Font, what if we want to use a custom font, say Avenir Next and support dynamic type at the same time?
In iOS 11, Apple has introduced UIFontMetrics class to let us support dynamic type on custom font easily.
To support dynamic type on custom font, we can use the UIFontMetrics to bind a certain text style for a label font like this :
// Yes, I know using force unwrapping is bad
let font = UIFont(name: "AvenirNext-Regular", size: 18.0)!
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)
This will make the label's font scale accordingly to the percentage increase of the body text style. If the body text style point size increases 35% (eg: 17pt to 23pt) when user switch from the default text size to the largest text size, UIFontMetrics will return the AvenirNext font with 35% larger size, ie. 18 * 1.35 = 24pt.
Even though UIFontMetrics has done most of the work for us, we still need to decide what font (and size) to use for each of the text style on the default text size (eg: AvenirNext Regular size 18.0 for the default text size of body text style). To simplify this process, I recommend following Keith Harrison's Plist approach on storing different fonts for different text styles.
Adapting label to larger font size
You might used to only set the top and leading constraints for a label like this :
As the label doesn't have a trailing constraint, it might expand over the available screen size if user selects a larger text size, causing the user unable to see the full text :
To solve this, we can add a trailing constraint to the label to ensure it won't expand beyond the screen, and set its Lines properties to 0 so that it can wrap the text to next line when there is not enough space in the current line.
After adding trailing constraint and setting number of lines to 0 , it looks good on larger text size like this :
Detecting text size changes
Sometimes we might want to perform certain action when user adjusted the text size in the Settings app, we can do so by implementing the traitCollectionDidChange(_ previousTraitCollection:) method. This method will be called when user return back to our app after changing text size.
// YourViewController.swift
class YourViewController : UIViewController {
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// perform action here
}
}
Content Size Category (List of different text sizes)
We can get the current selected text size by using the UIViewController.traitCollection.preferredContentSizeCategory property . This property will return the current text size (UIContentSizeCategory), with the default text size being .large (ie. the middle option in the text size slider) .
// YourViewController.swift
class YourViewController : UIViewController {
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// perform action here when user changes the text size
if self.traitCollection.preferredContentSizeCategory == .extraExtraExtraLarge {
// perform action here when user changes the text size to the largest (non-accessbility)
}
}
}
Here's some UIContentSizeCategory values relative to the text size slider :
You can view the full range of UIContentSizeCategory in Apple's documentation.
Adjusting other UI elements on text size changes
Often, there will be a mix of images and labels in a layout, sometimes we might want an image to grow in size following the text size increase. Similar to custom fonts, we can use the UIFontMetrics class, but this time with the method .scaledValue(for:) to scale the image size according to the percentage increase of the text size.
Say we want to bind the image height to the headline text style, and the default value for image height is 75.0, we can bind it like this :
// YourViewController.swift
// the height constraint for the avatar image view
@IBOutlet weak var avatarHeightConstraint: NSLayoutConstraint!
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// if the headline text size increase by 35%, the avatar height will increase by 35% too.
let adjustedHeight = UIFontMetrics(forTextStyle: .headline).scaledValue(for: 75.0)
avatarHeightConstraint.constant = adjustedHeight
}
Here's the example result :
“dude… wow… good job on this Auto Layout book! One of the best explanations I’ve read!” – Alex Kluew
Further Reading
You can check all the point size for different text styles at different system text size on Apple Human Interface Guideline's typography page.
Detailed tutorial on supporting dynamic type for custom fonts by Keith Harrison.