37

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.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
Samuel
  • 679
  • 1
  • 7
  • 13
  • 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)` – Felix Jun 17 '15 at 09:15
  • 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. – Samuel Jun 18 '15 at 11:17
  • 1
    This got fixed with Xcode 7 beta 4. UIFont now has the same monospacedDigitsSystemFontOfSize:weight: method as NSFont. – Samuel Aug 13 '15 at 15:53
  • That's not the spelling. It's monospacedDigitSystemFontOfSize:weight: (no 's' after Digit). – RobertL Nov 28 '15 at 16:26
  • https://stackoverflow.com/a/50792536/3939807 See this answer. This works for any custom fonts. – Rubaiyat Jahan Mumu Jun 11 '18 at 07:46

8 Answers8

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)
Ric Santos
  • 15,419
  • 6
  • 50
  • 75
  • 2
    @RudolfAdamkovic That is correct. Monospaced digits are feature of system font (San Francisco) in particular, not something UIFont provides universally. – totocaster Apr 05 '17 at 06:25
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.

Valentin Shamardin
  • 3,569
  • 4
  • 34
  • 51
Rudolf Adamkovič
  • 31,030
  • 13
  • 103
  • 118
  • Wow, this actually DOES work! Many thanks for this workaround! – Samuel Jun 23 '15 at 14:21
  • 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://21402564 – Samuel Jun 23 '15 at 14:25
  • I wrote a UIFont extension that implements the missing functionality: https://gist.github.com/samuel-mellert/a0419641895ff4c2ba82 – Samuel Jun 26 '15 at 15:12
  • 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 final – Berik Jul 13 '15 at 15:14
  • @SamuelMellert Yup, same here. – Rudolf Adamkovič Aug 12 '15 at 14:28
  • 4
    This got fixed with Xcode 7 beta 4. UIFont now has the same monospacedDigitsSystemFontOfSize:weight: method as NSFont. – Samuel Aug 13 '15 at 15:52
  • @SamuelMellert Actually, Apple's version is currently limited to the system font only whereas this works for any font. – Rudolf Adamkovič Sep 27 '15 at 10:04
  • 1
    This also works on iOS < 9. Looks like all relevant API is 7.0+. – mattyohe Oct 06 '15 at 03:39
  • Nice solution, It also doesn't break anything in iOS 8. I call the extension functions on viewDidLoad. – samir105 Oct 24 '15 at 18:08
  • 5
    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. – dbmrq Mar 22 '16 at 07:22
  • 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.... ¯\\_(ツ)_/¯ – Ian May 06 '16 at 16:50
  • @Rudolf Adamkovic Can you do Objective-C version? – Shebuka Jun 09 '16 at 16:53
  • 2
    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. – Christopher Swasey Jul 22 '18 at 16:15
  • Hat off! Works great with the custom font I was using. – Johannes Apr 19 '21 at 09:04
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)
    }
}
Chuck Boris
  • 1,275
  • 1
  • 11
  • 9
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
            ]
        ]
    ]
samwize
  • 25,675
  • 15
  • 141
  • 186
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)
    }
}
Jawwad
  • 1,326
  • 2
  • 9
  • 18
  • 1
    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) – svarrall Mar 30 '16 at 16:43
  • Thanks @svarrall. Updated my answer to note that it only happens in Release builds for me as well. – Jawwad Mar 30 '16 at 17:00
  • 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. – svarrall Apr 20 '16 at 07:34
  • I further refactored the solution and it doesn't crash on start, neither in applicationDidEnterBackground. Check my answer.. – Chuck Boris Jul 26 '16 at 11:38
  • Same problem here. Only happens in Release builds. – KTZ Jul 30 '16 at 16:55
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)
vicegax
  • 4,709
  • 28
  • 37
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
    }
}
OgreSwamp
  • 4,602
  • 4
  • 33
  • 54
  • 1
    Not sure this is solving much as @Rudolf's is supported below 9.0. – mattyohe Oct 06 '15 at 03:43
  • Very sorry for misleading comment, you're right. Don't know why but I thought that `monospacedDigitFontDescriptor` belongs to the system API. – OgreSwamp Oct 06 '15 at 17:32
-3

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

Andrew Smith
  • 2,919
  • 2
  • 22
  • 25