Text Kit and Dynamic Type in iOS

* This tutorial uses Swift *

What is Dynamic Type and why should I care?

Dynamic Type refers to dynamically adjusting the font family, font size when the user requests a bigger/smaller/bolder font in iOS Accessibility settings for his/her reading comfort.

Adopting Dynamic Type in your application enables iOS to enhance the legibility of texts thus making it convenient for users to read text in your application.

So lets start over 🙂

Implementing Dynamic Text in your application:

1. What is already available and what is not

If you are using UITableView in your application with standard cells, you might be surprised if I tell you that they already use Dynamic Type for title and sub titles. To verify this, follow the below steps. (If you don’t have a project already to test, you may download a sample reminder application called iRemind.)

This application has below two screens as under normal settings.

DynamicText_2.1 DynamicText_2.2

Testing: Changing text size for accessibility in iOS 8

Open the Settings app and navigate to General -> Accessibility -> Larger Text. Enable “Larger Accessibility Sizes” and use the slider to increase/decrease the sizes.

DynamicText_1.1
Normal Setting
DynamicText_1.2
Larger Font

You can now re-run the application to check which all components already adopt to the performed changes.

2. Using Dynamic Type for Common UI Elements

To make use of Dynamic Types, we need to specify a Font Style instead of Font Name and Size which allows iOS to select the appropriate Font Sizes/Types based on the user’s preference. For UI elements such as UILabel, UITextField, UITextView and so on, using dynamic Type is as simple as it can be.

Using Storyboards/XIB’s:

If you are using Storyboards or XIB’s, we can set it in the Attributes Inspector as shown below:

  • Select the UILabel (for example)
  • Select the Attributes Inspector button from the Utilities pane
  • Select the font and set it to one of the given Text Styles

DynamicText_0

On selection of these Text Styles, you will notice that you are no more allowed to select the Font Family or Size. Which is because these are automatically adjusted based on the user’s preferences.

Programatically Setting the Fonts:

For setting the values programmatically, set the font of the UILabel (for example) to the one of the predefined values as below.

    override func viewDidLoad() {
        super.viewDidLoad()
        // Adjust font for UI elements
        self.nameTextField.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption1)
        self.detailsTextView.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption1)
        self.nameLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption1)
        self.detailsLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption1)
        self.timeLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption1)
    }

UIFont has a function preferredFontForTextStyle() which returns the correct font based on the TextStyle as per the user’s accessibility preference.

Below are the available font styles:

    // Font text styles, semantic descriptions of the intended use for a font returned by +[UIFont preferredFontForTextStyle:]
    // UIFontTextStyleHeadline
    // UIFontTextStyleBody
    // UIFontTextStyleSubheadline
    // UIFontTextStyleFootnote
    // UIFontTextStyleCaption1
    // UIFontTextStyleCaption2

Once the changes are done, run the application after setting a larger text in Settings.

Note: The change will not be reflected if you are just re-launching the application from background to the same screen. This is because, when the UI element is created, it will use the appropriate font as per the settings. But once it is rendered on the screen, it is not clever enough to adjust the font. So re run the application by killing it from background after the settings have been changed.

To fix this issue, we will have to add a handling in our controller to set the correct fonts whenever the settings are changed.

3. Handling text size changes in Accessibility Settings

When the text sizes are changed in Accessibility Settings, we will have to just reset the font for the UI elements. When we reset the font, the new font is returned by the function preferredFontForTextStyle() based on the user preferences. Sample implementation below requires the following steps.

  • Adding the controller/view as observer for for changes in accessibility setting font size which is called by the name UIContentSizeCategoryDidChangeNotification for registering to the notification.
  • Resetting the fonts when the notification is received.
  • If you are using a table view, reloading the table view would also be required. Which in turn would reload the cells and reset the fonts.
    override func viewDidLoad() {
        super.viewDidLoad()
        // Adjust font for UI elements
        self.setFontForContentSizeChange(nil)
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        // Add observer to observe changes in text accessiblity
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "setFontForContentSizeChange:", name: UIContentSizeCategoryDidChangeNotification, object: nil)
    }
    
    override func viewDidDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        // Remove observer
        NSNotificationCenter.defaultCenter().removeObserver(self, name: UIContentSizeCategoryDidChangeNotification, object: nil)
    }

    func setFontForContentSizeChange(notification: NSNotification?) {
        self.nameTextField.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
        self.detailsTextView.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
        self.nameLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
        self.detailsLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
        self.timeLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
    }

Below are the screenshots from the sample application after the changes.

DynamicText_4.1 DynamicText_4.2

Wait. where’s the source code for these changes? Are you not gonna share that? Is that a NO? Well, Seriously? 😉

4. Custom implementation to support custom font

It is recommended to use the predefined font styles but in cases where you need to use custom fonts, we can still support dynamic type with the help of the notification that is posted on content size change. Since this is not quite straight forward, one option would be to use the values in the userInfo dictionary of the notification object to change the fonts in the application.

The notification object includes the following details.

{
    name = UIContentSizeCategoryDidChangeNotification;
    object = <UIApplication: 0x7fb852e12f30>;
    userInfo = {
        UIContentSizeCategoryNewValueKey = UIContentSizeCategoryAccessibilityExtraLarge;
        UIContentSizeCategoryTextLegibilityEnabledKey = 0;
    }
}

The various available values for content size categories being:

// Constants from UIApplication Class
// Content size category constants
let UIContentSizeCategoryExtraSmall: String
let UIContentSizeCategorySmall: String
let UIContentSizeCategoryMedium: String
let UIContentSizeCategoryLarge: String
let UIContentSizeCategoryExtraLarge: String
let UIContentSizeCategoryExtraExtraLarge: String
let UIContentSizeCategoryExtraExtraExtraLarge: String

// Accessibility sizes
let UIContentSizeCategoryAccessibilityMedium: String
let UIContentSizeCategoryAccessibilityLarge: String
let UIContentSizeCategoryAccessibilityExtraLarge: String
let UIContentSizeCategoryAccessibilityExtraExtraLarge: String
let UIContentSizeCategoryAccessibilityExtraExtraExtraLarge: String

I am gonna leave this implementation to you 🙂

5. Adjusting the font size for dynamic text in constrained layouts

For dynamic texts or translations that are difficult to fit in the given frame, you can use the below property and provide a minimum font size/scale  and the OS will automatically scale the text to fit any given width. (And Yes! if the text is too long it will definitely be truncated. ;))

UITextField has the below two properties to specify if you want the text to adjust the font automatically.

    adjustsFontSizeToFitWidth // Set this Bool to true to allow shrinking of text.
    minimumFontSize // Specify the minimum font values here.

For UILabel has the bellow property to set the scale factor. 0.0 being the default value and the current font size being the lowest font size.

    minimumScaleFactor // Set the value as a fraction of the font to be considered the minimum value.

This can also be done in the storyboard as shown below:

  • Select Autoshrink (Default value is preselected as Fixed Font Size).
  • Change it to Minimum Font Scale/Size.
  • Set the value for Scale/Size.
  • Optional: Select Tighten Letter Spacing to squeeze in more text.

DynamicText_5

This tutorial complements the previous tutorials on Internationalization and Localization in iOS and Accessibility in iOS: VoiceOver. Do check them out! 🙂

References:

  1. Apple: Text Programming Guide for iOS
  2. Ray Wenderlich Tutorial: Text Kit Tutorial: Getting Started
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s