How to get monospaced numbers in UILabel on iOS 9
Asked Answered
A

8

38

At WWDC 2015, there was a session about the new “San Francisco” system font in iOS 9. It uses proportional number rendering instead of monospaced numbers by default when linked against the iOS 9 SDK. There is a convenient initializer on NSFont called NSFont.monospacedDigitsSystemFontOfSize(mySize weight:) that can be used to explicitly enable monospaced number display.

However I couldn't find the UIKit equivalent for this on UIFont.

Airla answered 15/6, 2015 at 20:46 Comment(5)
tried using a font descriptor in a playground, but this is crashing with EXC_BAD_ACCESS in Xcode 7 Beta: var fontDescriptor = UIFontDescriptor().fontDescriptorWithSymbolicTraits(.TraitMonoSpace)Dorcia
I did manage to create a UIFontDescriptor with .TraitMonoSpace like you mentioned, however this seems not to be the solution to this issue, since I am actually not trying to assign a special monospaced font to the whole label, but to change it's digit rendering behavior so it renders numbers in a monospaced mode.Airla
This got fixed with Xcode 7 beta 4. UIFont now has the same monospacedDigitsSystemFontOfSize:weight: method as NSFont.Airla
That's not the spelling. It's monospacedDigitSystemFontOfSize:weight: (no 's' after Digit).Faceharden
https://mcmap.net/q/275412/-ios-monospaced-custom-font See this answer. This works for any custom fonts.Edifice
C
54

Handy UIFont extension:

extension UIFont {
    var monospacedDigitFont: UIFont {
        let newFontDescriptor = fontDescriptor.monospacedDigitFontDescriptor
        return UIFont(descriptor: newFontDescriptor, size: 0)
    }
}

private extension UIFontDescriptor {
    var monospacedDigitFontDescriptor: UIFontDescriptor {
        let fontDescriptorFeatureSettings = [[UIFontDescriptor.FeatureKey.featureIdentifier: kNumberSpacingType,
                                              UIFontDescriptor.FeatureKey.typeIdentifier: kMonospacedNumbersSelector]]
        let fontDescriptorAttributes = [UIFontDescriptor.AttributeName.featureSettings: fontDescriptorFeatureSettings]
        let fontDescriptor = self.addingAttributes(fontDescriptorAttributes)
        return fontDescriptor
    }
}

Usage with @IBOutlet properties:

@IBOutlet private var timeLabel: UILabel? {
    didSet {
        timeLabel.font = timeLabel.font.monospacedDigitFont
    }
}

Latest version on GitHub.

Columbarium answered 22/6, 2015 at 13:45 Comment(13)
Since I still believe this to be a bug or at least an oversight in the current API release I have filed this as a bug and published it on OpenRadar as rdar://21402564Airla
I wrote a UIFont extension that implements the missing functionality: gist.github.com/samuel-mellert/a0419641895ff4c2ba82Airla
That's a lot of code for something that will be desired quite common, I do hope there will be an update on this before iOS9 is finalBanderole
@SamuelMellert Yup, same here.Modestamodeste
This got fixed with Xcode 7 beta 4. UIFont now has the same monospacedDigitsSystemFontOfSize:weight: method as NSFont.Airla
@SamuelMellert Actually, Apple's version is currently limited to the system font only whereas this works for any font.Modestamodeste
This also works on iOS < 9. Looks like all relevant API is 7.0+.Bidle
Nice solution, It also doesn't break anything in iOS 8. I call the extension functions on viewDidLoad.Guessrope
Thank you, @SamuelMellert. This info should be added to the answer. I was using Rudolf's code and it broke after I upgraded to Xcode 7.3 today. And it was pretty hard to debug; it only crashes when building for release.Sarto
This solution also broke for me in Xcode 7.3 and only when building for release. However, in 'monospacedDigitFontDescriptor' if you print(fontDescriptor) before returning it, the extension works fine.... ¯\_(ツ)_/¯Cession
@Rudolf Adamkovic Can you do Objective-C version?Ean
It's worth noting, so people don't bang their head against the wall, that this won't necessarily work for arbitrary fonts. If the font doesn't have monospaced digit support then the added feature attributes won't have any effect.Elmore
Hat off! Works great with the custom font I was using.Anyplace
H
58

This is now available in UIFont since iOS 9:

+ (UIFont *)monospacedDigitSystemFontOfSize:(CGFloat)fontSize weight:(CGFloat)weight NS_AVAILABLE_IOS(9_0);

eg:

[UIFont monospacedDigitSystemFontOfSize:42.0 weight:UIFontWeightMedium];

or in Swift:

UIFont.monospacedDigitSystemFont(ofSize: 42.0, weight: UIFontWeightMedium)
Hage answered 29/8, 2015 at 11:42 Comment(1)
@RudolfAdamkovic That is correct. Monospaced digits are feature of system font (San Francisco) in particular, not something UIFont provides universally.Bugbee
C
54

Handy UIFont extension:

extension UIFont {
    var monospacedDigitFont: UIFont {
        let newFontDescriptor = fontDescriptor.monospacedDigitFontDescriptor
        return UIFont(descriptor: newFontDescriptor, size: 0)
    }
}

private extension UIFontDescriptor {
    var monospacedDigitFontDescriptor: UIFontDescriptor {
        let fontDescriptorFeatureSettings = [[UIFontDescriptor.FeatureKey.featureIdentifier: kNumberSpacingType,
                                              UIFontDescriptor.FeatureKey.typeIdentifier: kMonospacedNumbersSelector]]
        let fontDescriptorAttributes = [UIFontDescriptor.AttributeName.featureSettings: fontDescriptorFeatureSettings]
        let fontDescriptor = self.addingAttributes(fontDescriptorAttributes)
        return fontDescriptor
    }
}

Usage with @IBOutlet properties:

@IBOutlet private var timeLabel: UILabel? {
    didSet {
        timeLabel.font = timeLabel.font.monospacedDigitFont
    }
}

Latest version on GitHub.

Columbarium answered 22/6, 2015 at 13:45 Comment(13)
Since I still believe this to be a bug or at least an oversight in the current API release I have filed this as a bug and published it on OpenRadar as rdar://21402564Airla
I wrote a UIFont extension that implements the missing functionality: gist.github.com/samuel-mellert/a0419641895ff4c2ba82Airla
That's a lot of code for something that will be desired quite common, I do hope there will be an update on this before iOS9 is finalBanderole
@SamuelMellert Yup, same here.Modestamodeste
This got fixed with Xcode 7 beta 4. UIFont now has the same monospacedDigitsSystemFontOfSize:weight: method as NSFont.Airla
@SamuelMellert Actually, Apple's version is currently limited to the system font only whereas this works for any font.Modestamodeste
This also works on iOS < 9. Looks like all relevant API is 7.0+.Bidle
Nice solution, It also doesn't break anything in iOS 8. I call the extension functions on viewDidLoad.Guessrope
Thank you, @SamuelMellert. This info should be added to the answer. I was using Rudolf's code and it broke after I upgraded to Xcode 7.3 today. And it was pretty hard to debug; it only crashes when building for release.Sarto
This solution also broke for me in Xcode 7.3 and only when building for release. However, in 'monospacedDigitFontDescriptor' if you print(fontDescriptor) before returning it, the extension works fine.... ¯\_(ツ)_/¯Cession
@Rudolf Adamkovic Can you do Objective-C version?Ean
It's worth noting, so people don't bang their head against the wall, that this won't necessarily work for arbitrary fonts. If the font doesn't have monospaced digit support then the added feature attributes won't have any effect.Elmore
Hat off! Works great with the custom font I was using.Anyplace
T
6

Accepted solution works great, but was crashing with compiler optimization set to Fast(default for Release builds). Rewrote the code like this and now it does not:

extension UIFont
{
    var monospacedDigitFont: UIFont
    {
        return UIFont(descriptor: fontDescriptor().fontDescriptorByAddingAttributes([UIFontDescriptorFeatureSettingsAttribute: [[UIFontFeatureTypeIdentifierKey: kNumberSpacingType, UIFontFeatureSelectorIdentifierKey: kMonospacedNumbersSelector]]]), size: 0)
    }
}
Tsunami answered 25/7, 2016 at 13:40 Comment(0)
F
5

There has been quite some renaming in Swift 4, so the attributes now looks like this:

    let fontDescriptorAttributes = [
        UIFontDescriptor.AttributeName.featureSettings: [
            [
                UIFontDescriptor.FeatureKey.featureIdentifier: kNumberSpacingType,
                UIFontDescriptor.FeatureKey.typeIdentifier: kMonospacedNumbersSelector
            ]
        ]
    ]
Fellow answered 28/5, 2018 at 8:59 Comment(0)
B
3

Note: The method in the currently accepted answer has started crashing for me in Xcode 7.3 (Swift 2.2), only in Release builds. Eliminating the intermediary monospacedDigitFontDescriptor extension variable fixes the issue.

extension UIFont {
    var monospacedDigitFont: UIFont {
        let fontDescriptorFeatureSettings = [[UIFontFeatureTypeIdentifierKey: kNumberSpacingType, UIFontFeatureSelectorIdentifierKey: kMonospacedNumbersSelector]]
        let fontDescriptorAttributes = [UIFontDescriptorFeatureSettingsAttribute: fontDescriptorFeatureSettings]
        let oldFontDescriptor = fontDescriptor()
        let newFontDescriptor = oldFontDescriptor.fontDescriptorByAddingAttributes(fontDescriptorAttributes)

        return UIFont(descriptor: newFontDescriptor, size: 0)
    }
}
Bryannabryansk answered 29/3, 2016 at 2:33 Comment(5)
This also fixed the crashes for me. However the crashes were only happening on a release build with the swift compiler optimization level set to Fast (default for Release Builds)Polymerize
Thanks @svarrall. Updated my answer to note that it only happens in Release builds for me as well.Bryannabryansk
Unfortunately, this didn't resolve things totally. I've just tracked down a strange crash in applicationDidEnterBackground that is caused by this code, again only in release. monospacedDigitsSystemFontOfSize:weight: seems the only safe option at the moment.Polymerize
I further refactored the solution and it doesn't crash on start, neither in applicationDidEnterBackground. Check my answer..Tsunami
Same problem here. Only happens in Release builds.Turgescent
M
2

Example usage for Swift 5.2 following the accepted answer using dynamic type.

label.font = .init(descriptor: UIFont.preferredFont(forTextStyle: .body)
                 .fontDescriptor.addingAttributes([
                 .featureSettings: [[
                     UIFontDescriptor.FeatureKey.featureIdentifier: kNumberSpacingType,
                                                .typeIdentifier: kMonospacedNumbersSelector]]]),
                                                size: 0)

Worth mentioning for macOS (AppKit) it is slightly different:

NSFont(descriptor: NSFont.systemFont(ofSize: 20).fontDescriptor
       .addingAttributes([.featureSettings: [[NSFontDescriptor.FeatureKey
       .selectorIdentifier: kMonospacedNumbersSelector,
       .typeIdentifier: kNumberSpacingType]]]), size: 0)
Morly answered 26/5, 2020 at 12:10 Comment(0)
A
0

A bit improved version of the @Rudolf Adamkovic code which checks iOS version:

var monospacedDigitFont: UIFont {

    if #available(iOS 9, *) {
        let oldFontDescriptor = fontDescriptor()
        let newFontDescriptor = oldFontDescriptor.monospacedDigitFontDescriptor

        return UIFont(descriptor: newFontDescriptor, size: 0)
    } else {
       return self
    }
}
Aquarium answered 2/10, 2015 at 15:40 Comment(2)
Not sure this is solving much as @Rudolf's is supported below 9.0.Bidle
Very sorry for misleading comment, you're right. Don't know why but I thought that monospacedDigitFontDescriptor belongs to the system API.Aquarium
I
-3

Or, just use Helvetica. It still has monospaced numbers and works retroactively to older iOS version.

Immortality answered 14/4, 2016 at 17:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.