Как получить одноразмерные числа в UILabel на iOS 9

На WWDC 2015 состоялся сеанс, посвященный новому системному шрифту "Сан-Франциско" в iOS 9. В нем по умолчанию используется пропорциональное отображение чисел вместо разнесенных чисел при связывании с iOS 9 SDK. На NSFont есть удобный инициализатор, который называется NSFont.monospacedDigitsSystemFontOfSize(mySize weight:) это можно использовать для явного включения отображения моноширинного числа.

Однако я не мог найти UIKit эквивалент для этого на UIFont,

7 ответов

Решение

Удобный UIFont расширение:

extension UIFont {

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

}

private extension UIFontDescriptor {

    var monospacedDigitFontDescriptor: UIFontDescriptor {
        let fontDescriptorFeatureSettings = [[UIFontFeatureTypeIdentifierKey: kNumberSpacingType, UIFontFeatureSelectorIdentifierKey: kMonospacedNumbersSelector]]
        let fontDescriptorAttributes = [UIFontDescriptorFeatureSettingsAttribute: fontDescriptorFeatureSettings]
        let fontDescriptor = self.fontDescriptorByAddingAttributes(fontDescriptorAttributes)
        return fontDescriptor
    }

}

Использование с @IBOutlet свойства:

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

Последняя версия на GitHub.

Теперь это доступно в UIFont начиная с iOS 9:

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

например:

[UIFont monospacedDigitSystemFontOfSize:42.0 weight:UIFontWeightMedium];

или в Swift:

UIFont.monospacedDigitSystemFont(ofSize: 42.0, weight: UIFontWeightMedium)

Принятое решение прекрасно работает, но было сбой при оптимизации компилятора, установленной на Быстрое (по умолчанию для сборок выпуска). Переписал код, подобный этому, и теперь его нет:

extension UIFont
{
    var monospacedDigitFont: UIFont
    {
        return UIFont(descriptor: fontDescriptor().fontDescriptorByAddingAttributes([UIFontDescriptorFeatureSettingsAttribute: [[UIFontFeatureTypeIdentifierKey: kNumberSpacingType, UIFontFeatureSelectorIdentifierKey: kMonospacedNumbersSelector]]]), size: 0)
    }
}

В Swift 4 было довольно много переименований, поэтому атрибуты теперь выглядят так:

    let fontDescriptorAttributes = [
        UIFontDescriptor.AttributeName.featureSettings: [
            [
                UIFontDescriptor.FeatureKey.featureIdentifier: kNumberSpacingType,
                UIFontDescriptor.FeatureKey.typeIdentifier: kMonospacedNumbersSelector
            ]
        ]
    ]

Примечание: метод в текущем принятом ответе начал падать для меня в Xcode 7.3 (Swift 2.2), только в сборках выпуска. Устранение посредника monospacedDigitFontDescriptor Переменная расширения устраняет проблему.

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)
    }
}

Пример использования Swift 5.2 после принятого ответа с использованием динамического типа.

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

Стоит отметить, что для macOS (AppKit) он немного отличается:

NSFont(descriptor: NSFont.systemFont(ofSize: 20).fontDescriptor
       .addingAttributes([.featureSettings: [[NSFontDescriptor.FeatureKey
       .selectorIdentifier: kMonospacedNumbersSelector,
       .typeIdentifier: kNumberSpacingType]]]), size: 0)

Немного улучшенная версия кода @Rudolf Adamkovic, которая проверяет версию iOS:

var monospacedDigitFont: UIFont {

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

        return UIFont(descriptor: newFontDescriptor, size: 0)
    } else {
       return self
    }
}

Или просто используйте Helvetica. Он по-прежнему имеет одноразмерные числа и задним числом работает с более старой версией iOS.

Другие вопросы по тегам