Proportional vs. Monospaced Numbers: When to use which one in order to avoid "Wiggling Labels"

extension UIFont {

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

}

extension UIFontDescriptor {

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

SwiftUI

SwiftUIには monospacedDigit が存在しているが、0の表示がスラッシュ入りの0となってしまうためCTFontから同様の方法でFontを作成。

let attributes = [
  kCTFontFeatureSettingsAttribute : [
    kCTFontFeatureTypeIdentifierKey: kNumberSpacingType,
    kCTFontFeatureSelectorIdentifierKey : kMonospacedNumbersSelector
  ]
]
let descriptor = CTFontDescriptorCreateWithAttributes(attributes as CFDictionary)
let font = CTFontCreateWithFontDescriptor(descriptor, 16, nil)

let f = Font(font)

というか、UIFontとCTFontはそのままキャスト可能だった。 ので、次のようにまとめた

extension Font {

  static func customMonospacedDigit(size: CGFloat, weight: UIFont.Weight) -> Font {
    Font(UIFont.systemFont(ofSize: size, weight: weight).monospacedDigitFont() as CTFont)
  }

}

extension UIFont {

  func monospacedDigitFont() -> UIFont {

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

  }

}

extension UIFontDescriptor {

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