Supporting Dynamic Type

One of the more user visible changes in iOS 7 is the introduction of Dynamic Type. This provides the user with the ability to specify the preferred text size in the Settings app of the device. Applications that support dynamic type are then expected to adjust text to the preferred size:

Text Styles

Before getting too deep into dynamic text size I should briefly discuss text styles and preferred fonts. The UIFontDescriptor class was introduced with iOS 7 and provides a way to describe the attributes of a font such as the font family, size and whole set of other attributes. This makes it much easier to create new UIFont objects based on a common set of attributes. I don’t need to get into the details of UIFontDescriptor for this article but one of the attributes introduced with it was the concept of a text style.

A text style is actually just a pre-defined NSString constant that provides a short hand way of creating a UIFontDescriptor or by extension a UIFont with a predetermined set of attributes. The text styles introduced with iOS 7 are as follows (see the UIFontDescriptor class documentation for details):

  • UIFontTextStyleHeadline
  • UIFontTextStyleSubheadline
  • UIFontTextStyleBody
  • UIFontTextStyleFootnote
  • UIFontTextStyleCaption1
  • UIFontTextStyleCaption2

The intended uses for these text styles are for the most part self explanatory. The UIFont class includes a class method +preferredFontForTextStyle: that returns an instance of the font specified with the specified text style. For example, the following code snippet will return a font suitable for use a headline:

UIFont *myFont = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];

The important point to understand with these preferred fonts is that Apple takes care of deciding all of the details for the font including the text size that the user has specified in the device settings. The screenshots below show how the font for each text style changes for the smallest, medium and largest setting of the text size:

In iOS 7 the Helvetica Neue font family is used for the preferred font and Apple takes care of sizing, spacing and other attributes to make the text legible for all text styles and sizes. For somebody like me who is font challenged this is most likely a good thing. Making decisions about fonts for a variety of styles and sizes is not trivial and is likely to lead to the inappropriate use of Comic Sans.

There is full support for text styles in Interface Builder when creating UIKit controls. For example for a UILabel object the font setting in the attributes inspector now allows you to directly set the text style used for the label:

Implementing Support For Dynamic Type

To show how easy it can be to support Dynamic Type I have created a trivial example app that consists of a single view containing a UILabel for each of the possible text styles. The view controller as it appears in Interface Builder is shown below:

Each text label has the corresponding text style directly set in Interface Builder. I should take the opportunity at this point to mention Auto Layout. It is not mandatory to use Auto Layout with preferred fonts but it does make life a whole lot easier. With dynamic type and preferred fonts the size of a text field can change anytime the user changes the text size setting. For all but the most simple of layouts relying on springs and struts to cope with this is likely to be a frustrating experience.

I am not going to cover Auto Layout in this post but I did leave the first text field in the previous screenshot selected to show the constraints it is using. Each UILabel (except the last) has four constraints as follows:

  • A leading space constraint to the superview (left hand margin)
  • A trailing space constraint to the superview (right hand margin)
  • A top space constraint to maintain separation to the UILabel above. The top label has the constraint with the top layout guide.
  • A bottom space to maintain separation to the UILabel below (the last UILabel does not need this constraint).

These constraints keep our UILabel fields nicely aligned however big or small the text becomes.

Running the app at this point will already show the preferred fonts for each of the text styles at whatever text size preference is set when the app launches. However if we send the app to the background, switch to the settings app to change the text size and then switch back to the app nothing happens.

Detecting When The User Changes The Text Size

Update 14 Aug 2017: If your minimum target is iOS 10 or later you can avoid having to listen for the notification and manually updating the fonts by setting adjustsFontForContentSizeCategory on each label, text field or text label.

To ensure we get notified when the user changes the text size for a running app we need to listen for the UIContentSizeCategoryDidChangeNotification. A good place to do that is in the viewDidLoad method of the view controller:

- (void)viewDidLoad {
  [super viewDidLoad];
  [[NSNotificationCenter defaultCenter] addObserver:self

We also need to make sure we remove the observer when the view controller is released:

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];

Update 14 Aug 2017: If your minimum target is iOS 9 or later you do not need to unregister an observer in dealloc.

Now when the user changes the text size we can force a refresh of the user interface. In this very simple example we fully configured the user interface in Interface Builder. To allow the interface to be refreshed from the view controller I will first add an outlet property to the view controller for each of the labels:

@property (weak, nonatomic) IBOutlet UILabel *textSizeLabel;
@property (weak, nonatomic) IBOutlet UILabel *headlineLabel;
@property (weak, nonatomic) IBOutlet UILabel *subheadLabel;
@property (weak, nonatomic) IBOutlet UILabel *bodyLabel;
@property (weak, nonatomic) IBOutlet UILabel *caption1Label;
@property (weak, nonatomic) IBOutlet UILabel *caption2Label;
@property (weak, nonatomic) IBOutlet UILabel *footnoteLabel;

Then in a configureView method I will explicitly configure each of the labels with the preferred font using the preferredFontForTextStyle: method we saw earlier:

- (void)configureView {
  self.textSizeLabel.text = [[UIApplication sharedApplication] preferredContentSizeCategory];
  self.headlineLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
  self.subheadLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
  self.bodyLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
  self.caption1Label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1];
  self.caption2Label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2];
  self.footnoteLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];

Note that I also set the textSizeLabel in the view controller to show the current value of the UIApplication preferredContentSizeCategory property so we can see what is happening.

Now when we receive the notification that the user has changed the content size we can use our configureView method to update the preferred font used for each of the UILabel objects:

- (void)didChangePreferredContentSize:(NSNotification *)notification {
  [self configureView];

For completeness I also call this method from viewDidAppear: to ensure the view is correctly configured when the view appears on screen.

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  [self configureView];

Now if the user changes the text size whilst the app is running in the background the view controller is notified when the app returns to the foreground and updates the font for each of the UILabel objects. Screenshots for the extra-small, large and extra-extra-extra-large content sizes are shown below:

What about other fonts?

As the name suggests the built-in UIKit preferred font text styles only work for the UIKit preferred font which is fine if you like Helvetica Neue. If you want to support dynamic text sizes in your app using a different font there is nothing to stop you listening to the UIContentSizeCategoryDidChangeNotification and then adjusting the font size of your own preferred font. However I suspect it may be easier to just implement a text size setting directly in the App and ignore the value in the device Settings.

Update 14 Aug 2017: Using custom fonts with dynamic type has gotten a lot easier with iOS 11. See this post for details:

Wrapping Up

You can find the example Dynamic Text Xcode project in my GitHub CodeExamples repository.

If you want to see how to do this in Swift and learn more about using dynamic type to build adaptive layouts take a look at my book Modern Auto Layout.