如何在iOS 9上使UILabel中的数字等宽

37

尝试在Playground中使用字体描述符,但在Xcode 7 Beta中崩溃并显示EXC_BAD_ACCESS错误:var fontDescriptor = UIFontDescriptor().fontDescriptorWithSymbolicTraits(.TraitMonoSpace) - Felix
我确实成功创建了一个UIFontDescriptor,并使用.TraitMonoSpace属性,就像你提到的那样。然而,这似乎并不是解决此问题的方法,因为我实际上并没有尝试将特殊的等宽字体分配给整个标签,而是要更改其数字渲染行为,以便以等宽模式呈现数字。 - Samuel
1
这个问题在Xcode 7 beta 4中得到了修复。UIFont现在有了与NSFont相同的monospacedDigitsSystemFontOfSize:weight:方法。 - Samuel
那个不是正确的拼写。它应该是monospacedDigitSystemFontOfSize:weight:(在Digit后面没有's')。 - RobertL
查看此答案。这适用于任何自定义字体。 - Rubaiyat Jahan Mumu
8个回答

58

自iOS 9起,现在可以在UIFont中使用此功能:

+ (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)

2
@RudolfAdamkovic 没错。等宽数字是系统字体(特别是旧金山字体)的一个特性,而不是 UIFont 通用提供的功能。 - totocaster

54

便利的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 上最新版本请查看此处


哇,这真的有效!非常感谢这个解决方法! - Samuel
4
这个问题在Xcode 7 beta 4中得到了解决。UIFont现在拥有与NSFont相同的monospacedDigitsSystemFontOfSize:weight:方法。 - Samuel
1
这在 iOS < 9 上也适用。看起来所有相关的 API 都是 7.0+。 - mattyohe
5
感谢您,@SamuelMellert。这份信息应该被添加到答案中。我正在使用Rudolf的代码,并且在今天升级到Xcode 7.3后它出了问题。而且调试起来非常困难;只有在进行发布构建时才会崩溃。 - dbmrq
2
值得注意的是,为了避免让人们撞墙,这并不一定适用于任意字体。如果字体没有等宽数字支持,则添加的特性属性将没有任何效果。 - Christopher Swasey
显示剩余9条评论

6

这个被接受的解决方案非常好,但是当编译器优化设置为快(默认为发布版本)时会崩溃。将代码重写如下后就不会出现这种情况了:

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

5

Swift 4中进行了相当多的重命名,因此现在属性看起来像这样:

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

3

注意:目前被接受的答案中的方法在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)
    }
}

1
这也为我解决了崩溃问题。然而,这些崩溃只发生在使用 Swift 编译器优化级别设置为 Fast(发布版本的默认设置)的情况下。 - svarrall
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Jawwad
不幸的是,这并没有完全解决问题。我刚刚在 applicationDidEnterBackground 中追踪到一个奇怪的崩溃,再次只出现在发布版本中。目前看来,monospacedDigitsSystemFontOfSize:weight: 似乎是唯一安全的选择。 - svarrall
我进一步重构了解决方案,现在它在启动时和applicationDidEnterBackground中都不会崩溃。请查看我的答案。 - Chuck Boris
这里也有同样的问题。只在发布版本中发生。 - KTZ

2

使用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)

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

1
不确定这是否解决了问题,因为@Rudolf的版本在9.0以下也得到支持。 - mattyohe
非常抱歉误导了您,您是正确的。不知道为什么,但我认为monospacedDigitFontDescriptor属于系统API。 - OgreSwamp

-3

或者,只需使用Helvetica字体。它仍然具有等宽数字,并且可以向旧版iOS版本回溯。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接