Scaling Dynamic Type with Font Descriptors

In a previous post, supporting dynamic type, I covered how to respond to the user changing their preferred text size. This approach requires you to adopt the new text styles that were introduced with iOS 7. Unfortunately the implementation of text styles in iOS 7 does not allow you to change the font of the text styles from Helvetica Neue. However in this post I will cover a quick tip to easily scale the fonts that extends the usefulness of the built-in styles.

I am not going to cover the details of creating fonts for each of the text styles or how to register for the notification that the user has changed their preference. For those details refer back to the previous post and the example Dynamic Text Xcode project.

Font Descriptors

Previously to create a font for a given text style we used a class method of UIFont as follows (in this case for the headline style):

UIFont *myFont = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];

The UIFont class has an instance method fontWithSize: that can be used to return a new scaled font based on an existing font but a better approach that avoids having to create this intermediate font object is to make use of font descriptors. From the UIFontDescriptor class reference:

UIFontDescriptor objects provide a mechanism to describe a font with a dictionary of attributes. This font descriptor can be used later to create or modify a UIFont object. Font descriptors can be archived and unarchived.

Instead of creating a font for the text style we can create a font descriptor as follows:

UIFontDescriptor *descriptor = [UIFontDescriptor

We can now modify the font descriptor based on our requirements, for example making it bold or italic, before creating the final font object from the descriptor as follows:

UIFont *font = [UIFont fontWithDescriptor:descriptor size:pointSize];

In this example where we are only going to scale the font there is actually no need to modify the font descriptor so there is maybe not much to choose between these two approaches but in general the font descriptor is the more flexible approach.

Scaling the Font

For convenience I will create a category on UIFont for a method that will return the scaled font. The public interface for the category is shown below:

// UIFont+UYLScaledFont.h
@interface UIFont (UYLScaledFont)
+ (UIFont *)uylPreferredFontForTextStyle:(NSString *)style

The method takes a UIFont text style as defined in UIFontDescriptor.h and a scale factor used to modify the default size of the preferred font. The implementation of the method is almost trivial:

// UIFont+UYLScaledFont.m
@implementation UIFont (UYLScaledFont)
+ (UIFont *)uylPreferredFontForTextStyle:(NSString *)style
    scale:(CGFloat)scaleFactor {
  UIFontDescriptor *descriptor = [UIFontDescriptor
  CGFloat pointSize = descriptor.pointSize * scaleFactor;
  UIFont *font = [UIFont fontWithDescriptor:descriptor size:pointSize];
  return font;

As previously mentioned instead of directly retrieving the UIFont for the preferred text style we now use the UIFontDescriptor class method preferredFontDescriptorWithTextStyle: to first get the font descriptor. To return a scaled font we multiple the pointSize of the font descriptor by the scaleFactor and then use the UIFont class method fontWithDescriptor:size: to retrieve the actual scaled font that corresponds to the font descriptor.

Updating the User interface

To provide an example of how to apply the scaled fonts I have added a second view controller to the DynamicText example project. The UYLScaledTextStyleViewController is a subclass of the original UYLTextStyleViewController and differs only in the method that configures the view. The original method is reproduced below for reference:

- (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];

This method which is invoked when the view appears and any time the user changes the text size uses the UIFont class method preferredFontForTextStyle: to set the font for each of the text labels. The modified version that uses our method to get a scaled version of the fonts is shown below:

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

With a scaleFactor of 2.0 the fonts for each of the text styles are doubled in size. The original and new views are shown below for comparison.

original scaled

Some Limitations

When you use a UIFontDescriptor for one of the built-in text styles you are limited in which attributes you can modify. You can change any of the symbolic traits, so for example you could derive a new descriptor that was italic as follows:

UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:style];
UIFontDescriptor *myDescriptor = [descriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic];

However if you attempt to change the font family or face it does not have any effect. So for example the following code would still return a Helvetica Neue font:

UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:style];
UIFontDescriptor *myDescriptor = [descriptor fontDescriptorWithFamily:@"Verdana"];
UIFont *myFont = [UIFont fontWithDescriptor:myDescriptor size:0.0];

If you want a font descriptor for a different font you need to create it from scratch rather than starting from a descriptor based on a text style.

Wrapping Up

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

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