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

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

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


person Samuel Mellert    schedule 15.06.2015    source источник
comment
попытался использовать дескриптор шрифта на игровой площадке, но это приводит к сбою с EXC_BAD_ACCESS в Xcode 7 Beta: var fontDescriptor = UIFontDescriptor().fontDescriptorWithSymbolicTraits(.TraitMonoSpace)   -  person Felix    schedule 17.06.2015
comment
Мне удалось создать UIFontDescriptor с .TraitMonoSpace, как вы упомянули, однако это, похоже, не решение этой проблемы, поскольку я на самом деле не пытаюсь назначить специальный моноширинный шрифт для всей метки, а изменить его поведение рендеринга цифр поэтому он отображает числа в моноширинном режиме.   -  person Samuel Mellert    schedule 18.06.2015
comment
Это было исправлено в Xcode 7 beta 4. UIFont теперь имеет тот же метод monospacedDigitsSystemFontOfSize:weight:, что и NSFont.   -  person Samuel Mellert    schedule 13.08.2015
comment
Это не то написание. Это моноширинный DigitSystemFontOfSize:weight: (без буквы «s» после цифры).   -  person RobertL    schedule 28.11.2015
comment
stackoverflow.com/a/50792536/3939807 См. этот ответ. Это работает для любых пользовательских шрифтов.   -  person Rubaiyat Jahan Mumu    schedule 11.06.2018


Ответы (8)


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

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

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

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

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

person Rudolf Adamkovič    schedule 22.06.2015
comment
Вау, это действительно работает! Большое спасибо за этот обходной путь! - person Samuel Mellert; 23.06.2015
comment
Поскольку я все еще считаю, что это ошибка или, по крайней мере, недосмотр в текущей версии API, я зарегистрировал это как ошибку и опубликовал на OpenRadar как rdar://21402564. - person Samuel Mellert; 23.06.2015
comment
Я написал расширение UIFont, которое реализует недостающую функциональность: - person Samuel Mellert; 26.06.2015
comment
Это много кода для чего-то, что будет желать довольно часто, я надеюсь, что это будет обновление до того, как iOS9 станет финальной. - person Berik; 13.07.2015
comment
@SamuelMellert Да, здесь то же самое. - person Rudolf Adamkovič; 12.08.2015
comment
Это было исправлено в Xcode 7 beta 4. UIFont теперь имеет тот же метод monospacedDigitsSystemFontOfSize:weight:, что и NSFont. - person Samuel Mellert; 13.08.2015
comment
@SamuelMellert На самом деле версия Apple в настоящее время ограничена только системным шрифтом, тогда как это работает для любого шрифта. - person Rudolf Adamkovič; 27.09.2015
comment
Это также работает на iOS ‹ 9. Похоже, что все соответствующие API версии 7.0+. - person mattyohe; 06.10.2015
comment
Хорошее решение, оно также ничего не ломает в iOS 8. Я вызываю функции расширения для viewDidLoad. - person samir105; 24.10.2015
comment
Спасибо, @SamuelMellert. Эту информацию следует добавить к ответу. Я использовал код Рудольфа, и он сломался после того, как сегодня я обновился до Xcode 7.3. И это было довольно сложно отлаживать; он падает только при сборке для выпуска. - person dbmrq; 22.03.2016
comment
Это решение также сломалось для меня в Xcode 7.3 и только при сборке для выпуска. Однако в 'monospacedDigitFontDescriptor', если вы печатаете (fontDescriptor) перед его возвратом, расширение работает нормально.... ¯\_(ツ)_/¯ - person Ian; 06.05.2016
comment
@Rudolf Adamkovic Можете ли вы сделать версию Objective-C? - person Shebuka; 09.06.2016
comment
Стоит отметить, чтобы люди не бились головой о стену, что это не обязательно будет работать для произвольных шрифтов. Если шрифт не поддерживает моноширинные цифры, то добавленные атрибуты функций не будут иметь никакого эффекта. - person Christopher Swasey; 22.07.2018
comment
Снимаю шляпу! Отлично работает с пользовательским шрифтом, который я использовал. - person Johannes; 19.04.2021

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

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

eg:

[UIFont monospacedDigitSystemFontOfSize:42.0 weight:UIFontWeightMedium];

или в Свифте:

UIFont.monospacedDigitSystemFont(ofSize: 42.0, weight: UIFontWeightMedium)
person Ric Santos    schedule 29.08.2015
comment
@RudolfAdamkovic Это правильно. Моноширинные цифры являются особенностью системного шрифта (San Francisco), в частности, а не универсальной универсальной функцией UIFont. - person totocaster; 05.04.2017

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

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

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

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

Примечание. Метод в принятом в настоящее время ответе начал давать сбой в Xcode 7.3 (Swift 2.2), только в сборках Release. Устранение промежуточной переменной расширения 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)
    }
}
person Jawwad    schedule 29.03.2016
comment
Это также исправило сбои для меня. Однако сбои происходили только в сборке выпуска с быстрым уровнем оптимизации компилятора, установленным на «Быстрый» (по умолчанию для сборок выпуска). - person svarrall; 30.03.2016
comment
Спасибо @svarrall. Обновлен мой ответ, чтобы отметить, что это происходит только в сборках Release для меня. - person Jawwad; 30.03.2016
comment
К сожалению, это не решило ситуацию полностью. Я только что обнаружил странный сбой в applicationDidEnterBackground, вызванный этим кодом, опять же только в релизе. monospacedDigitsSystemFontOfSize:weight: кажется единственным безопасным вариантом на данный момент. - person svarrall; 20.04.2016
comment
Я дополнительно рефакторил решение, и оно не падает при запуске, ни в applicationDidEnterBackground. Проверьте мой ответ.. - person Chuck Boris; 26.07.2016
comment
Та же проблема здесь. Происходит только в релизных сборках. - person KTZ; 30.07.2016

Пример использования для 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)
person vauxhall    schedule 26.05.2020

Немного улучшенная версия кода @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
    }
}
person OgreSwamp    schedule 02.10.2015
comment
Не уверен, что это решает многое, поскольку @Rudolf поддерживается ниже 9.0. - person mattyohe; 06.10.2015
comment
Очень извиняюсь за вводящий в заблуждение комментарий, вы правы. Не знаю почему, но я думал, что monospacedDigitFontDescriptor относится к системному API. - person OgreSwamp; 06.10.2015

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

person Andrew Smith    schedule 14.04.2016