Developing for iOS 9: Supporting Dynamic Type and Responding to User Changes

Text Styles in iOS 9

Dynamic Type was introduced with iOS 7 as a way for developers to abstract font details like typeface, size, weight, and kerning into a series of pre-defined font categories called Text Styles. 

 

Working with Text Styles

iOS 7 included the following Text Styles:

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

With iOS 9, Apple added 4 new text styles:

  • UIFontTextStyleCallout
  • UIFontTextStyleTitle1
  • UIFontTextStyleTitle2
  • UIFontTextStyleTitle3

Each Text Style was designed for a specific use case. UIFontTextStyleBody is probably the most used Text Style and is meant for all body text such as the content in an email or the main text in a UITableViewCell. UIFontTextStyleFootnote is great for basic table view header text, and UIFontTextStyleCaption1 for footer text.

To create a font using a Text Style, UIFont provides the +preferredFontForTextStyle: class method which returns an instance of the specified font for the passed in style. For example:

UIFont *myFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

Apple will dynamically return the correct font based on both the Text Style you've specified in Xcode and the user's chosen text size preference in Settings.app. The exact font details shouldn't matter to you as long as you're using Auto Layout to ensure your views resize to fit the text. I'll assume you're are already using Auto Layout for the rest of this tutorial.

For each text view, text field, and label in your project, set the font property to the appropriate Text Style for its content. You can do this in code or in Interface Builder with the popover below.

Now run the app, move to Settings.app, change the text size preference, then come back to your app to… notice that nothing happened?

 

Responding to text size changes

Each time the user modifies the text size preference, UIKit sends a UIContentSizeCategoryDidChangeNotification notification . You'll need to respond to that notification and update the UI when the app returns from a background state in order for text to automatically resize. In the following example, we'll look at dynamically updating the font of multiple UILabels. The same steps apply to UITextField and UITextView.

In this example, we have an array of labels that all have their font property set to UIFontTextStyleCallout, and a nameLabel that uses UIFontTextStyleHeadline.

@property (strong, nonatomic) IBOutlet UILabel *nameLabel;
@property (strong, nonatomic) IBOutletCollection(UILabel) NSArray *labels;

Start by adding your class as an observer of the UIContentSizeCategoryDidChangeNotification notification. If your labels are owned by a UIViewController subclass, add the following snippet to your viewDidLoad: method. For UIView subclasses, add it to the awakeFromNib method.

- (void)awakeFromNib {
 [super awakeFromNib];
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preferredContentSizeChanged:) name:UIContentSizeCategoryDidChangeNotificationobject:nil];
}

In either case, be sure to also remove yourself as the notification observer in dealloc:

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

Next, add the preferredContentSizeChanged: method and call updateViews:

- (void)preferredContentSizeChanged:(NSNotification *)aNotification {
 [self updateViews];
}

- (void)updateViews {
 self.nameLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
 for (UILabel *label in self.labels) {
  label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCallout];
 }
}

I've used an IBOutletCollection to group labels using the same Text Style in an array. If that array contains 10 labels, I can set the Text Style on each of them in 3 lines of code rather than 10. It's a neat trick that can make your code easier to read and with complex layouts, less difficult to manage.

Each time the text size user preference is changed, updateViews: is called and that's where we reset the Text Style on each label. If you're using Auto Layout, this triggers your UI to readjust to fit the new size for its text.

If you're setting the font on your labels in code rather than Interface Builder, be sure to call updateViews from viewDidAppear: as well so the proper text size is used when the view is first loaded.

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

That's all there is to it! You'll now have text in your app that changes and readjusts its container based on the user's text size preference.