Use Your Loaf

iOS 9 Proportional Numbers

The WWDC 2015 session 804 Introducing the New System Fonts covers the new San Francisco font families that Apple is introducing with OS X 10.11, iOS 9 and watchOS. If you did not make it to the end of the session you may have missed another important change. The default for how numbers are displayed is changing from monospaced to proportional:

Proportional numbers by default, opt into monospaced

To see where this might not be what you want consider the following example. Two static text labels containing numbers would by default on iOS 8 be monospaced and align vertically:

iOS 8 monospaced numbers

Once this app is rebuilt with the iOS 9 SDK and run on an iOS 9 device the numbers are displayed using a proportional font which messes with the alignment:

iOS 9 proportional numbers

Changing Font Features in Code

Monospaced numbers are a font feature that can, in theory, be enabled in the Typography panel (accessed from the font panel, look for the Typography option under the settings menu). I say in theory as I am still using OS X 10.10 which does not have the new font panel.

Luckily the WWDC session also provides some code snippets for enabling features. The labels in my example are using the dynamic type text styles introduced with iOS 7. To modify the text style to enable monospaced numbers we first need to get a font descriptor:

let bodyFontDescriptor = UIFontDescriptor.preferredFontDescriptorWithTextStyle(UIFontTextStyleBody)

We then create a new font descriptor by adding an attribute to the body font descriptor that specifies the monospaced number feature:

let bodyMonospacedNumbersFontDescriptor = bodyFontDescriptor.fontDescriptorByAddingAttributes(
    [
      UIFontDescriptorFeatureSettingsAttribute: [
        [
          UIFontFeatureTypeIdentifierKey: kNumberSpacingType,
          UIFontFeatureSelectorIdentifierKey: kMonospacedNumbersSelector
        ]
      ]
    ])

Finally we create the font from the font descriptor and apply it to the label:

let bodyMonospacedNumbersFont = UIFont(descriptor: bodyMonospacedNumbersFontDescriptor, size: 0.0)
textLabel.font = bodyMonospacedNumbersFont

Notes:

  • The fontDescriptorByAddingAttributes method takes a dictionary of font attributes.
  • The UIFontDescriptorFeatureSettingsAttribute font attribute is an array of dictionaries containing non-default font feature settings. Each of the dictionaries (we only have one in this case) must contain UIFontFeatureTypeIdentifierKey and UIFontFeatureSelectorIdentifierKey.
  • The values to use for the font feature keys are defined in the Core Text header file SFNTLayoutTypes.h.

The final appearance on iOS 9 for a body text style with monospaced numbers is as follows:

iOS 9 monospaced numbers

Alternate Styles

As an added bonus there are also a couple of stylistic alternatives to the way the 4, 6 and 9 are displayed. You can enable these style alternatives in much the same way we enabled monospaced numbers by adding an attribute to a base font descriptor.

Alternate Style One (6 and 9)

This first alternate style changes both the 6 and 9:

let bodyAltStyle1Descriptor = bodyFontDescriptor.fontDescriptorByAddingAttributes(
    [
      UIFontDescriptorFeatureSettingsAttribute: [
        [
          UIFontFeatureTypeIdentifierKey: kStylisticAlternativesType,
          UIFontFeatureSelectorIdentifierKey: kStylisticAltOneOnSelector
        ]
      ]
    ])

let altStyle1BodyFont = UIFont(descriptor: bodyAltStyle1Descriptor, size: 0.0)
textLabel.font = altStyle1BodyFont

The changed visual appearance is shown below with the alternate 6 and 9 digits highlighted:

alternate 6,9

Alternate Style Two (4)

This second alternate style changes the 4:

let bodyAltStyle2Descriptor = bodyFontDescriptor.fontDescriptorByAddingAttributes(
    [
      UIFontDescriptorFeatureSettingsAttribute: [
        [
          UIFontFeatureTypeIdentifierKey: kStylisticAlternativesType,
          UIFontFeatureSelectorIdentifierKey: kStylisticAltTwoOnSelector
        ]
      ]
    ])

let altStyle2BodyFont = UIFont(descriptor: bodyAltStyle2Descriptor, size: 0.0)
txtLabel.font = altStyle2BodyFont

The changed visual appearance of the digit 4 is highlighted below:

alternate 4