2

This code below enabled me to create small caps text on iOS 12. However on iOS 13 it's stopped working.

iOS 12 log

.SFUIDisplay-Semibold
.SFUIDisplay-Semibold

iOS 13 log

.SFUI-Semibold
TimesNewRomanPSMT

It seems that the SFUI font has changed name at least in iOS 13, but has it also dropped support for small caps?

let font = UIFont.systemFont(ofSize: 20, weight: .semibold)
print(font.fontName)

let settings = [[UIFontDescriptor.FeatureKey.featureIdentifier: kLowerCaseType,
                 UIFontDescriptor.FeatureKey.typeIdentifier: kLowerCaseSmallCapsSelector]]

let attributes: [UIFontDescriptor.AttributeName: AnyObject] = [UIFontDescriptor.AttributeName.featureSettings: settings as AnyObject,
                                                               UIFontDescriptor.AttributeName.name: font.fontName as AnyObject]

let smallCapsFont = UIFont(descriptor: UIFontDescriptor(fontAttributes: attributes), size: 20)
print(smallCapsFont.fontName)
Niall Mccormack
  • 1,314
  • 13
  • 19

3 Answers3

3

Your code was always wrong. You start by creating a font and then you derive a font descriptor from it by passing through the font's name:

let font = UIFont.systemFont(ofSize: 20, weight: .semibold)
let settings = [
    [UIFontDescriptor.FeatureKey.featureIdentifier: kLowerCaseType,
    UIFontDescriptor.FeatureKey.typeIdentifier: kLowerCaseSmallCapsSelector]
]
let attributes: [UIFontDescriptor.AttributeName: AnyObject] = [
    UIFontDescriptor.AttributeName.featureSettings: settings as AnyObject,
    UIFontDescriptor.AttributeName.name: font.fontName as AnyObject // ** NO!
]
let smallCapsFont = UIFont(descriptor: UIFontDescriptor(fontAttributes: attributes), size: 20)

That was never correct. You should derive the font descriptor directly from the font itself. This is what your code should always have looked like. It worked in iOS 12 and it works now:

let font = UIFont.systemFont(ofSize: 20, weight: .semibold)
let settings = [
    [UIFontDescriptor.FeatureKey.featureIdentifier: kLowerCaseType,
    UIFontDescriptor.FeatureKey.typeIdentifier: kLowerCaseSmallCapsSelector]
]
let desc = font.fontDescriptor.addingAttributes([.featureSettings:settings])
let smallCapsFont = UIFont(descriptor: desc, size: 0)
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I was struggling with this with almost identical code, and after testing with yours I noticed I had `.featureIdentifier` and `.typeIdentifier` mixed up. Thanks! – Andreas Nov 27 '20 at 16:43
  • 1
    @Andreas They are terrible names! I have a bug filed on that. – matt Nov 27 '20 at 16:59
  • Nice to know I'm not alone. You wouldn't happen to know how to exclude digits from the effects of small caps? (Q: https://stackoverflow.com/q/65041221/220820) – Andreas Nov 27 '20 at 17:12
  • 1
    @Andreas You would just have to use a different "font" (i.e. a different variant). – matt Nov 27 '20 at 17:19
2

System fonts like this are now longer reference-able with 'dot' / . notation (even when recreating them like your font.fontName example) as per:

https://developer.apple.com/videos/play/wwdc2019/227/

Starting with iOS 13, you can utilise the new enum UIFontDescriptor.SystemDesign, using regular, rounded, serif or monospaced.

An example of how to create font using descriptors in Swift (see how I'm using the design parameter):

extension UIFont {

    convenience init?(
        style: UIFont.TextStyle,
        weight: UIFont.Weight = .regular,
        design: UIFontDescriptor.SystemDesign = .default) {

        guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style)
            .addingAttributes([UIFontDescriptor.AttributeName.traits: [UIFontDescriptor.TraitKey.weight: weight]])
            .withDesign(design) else {
                return nil
        }
        self.init(descriptor: descriptor, size: 0)
    }
}

Ash Cameron
  • 1,898
  • 1
  • 10
  • 11
0

Taking inspiration from @Ash Camerons answer, here is the updated code that allows you to apply small caps

Note, this only works in iOS 13+, but you could optionally add the .withDesign(systemDesign) line for iOS 13 and above.

let pointSize: CGFloat = 20
let weight: UIFont.Weight = .semibold
let systemDesign: UIFontDescriptor.SystemDesign = .default

let settings = [[UIFontDescriptor.FeatureKey.featureIdentifier: kLowerCaseType,
                 UIFontDescriptor.FeatureKey.typeIdentifier: kLowerCaseSmallCapsSelector]]

let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .callout)
        .addingAttributes([UIFontDescriptor.AttributeName.traits: [UIFontDescriptor.TraitKey.weight: weight]])
        .addingAttributes([UIFontDescriptor.AttributeName.featureSettings: settings])
        .addingAttributes([UIFontDescriptor.AttributeName.size: pointSize])
        .withDesign(systemDesign)

let font = UIFont(descriptor: descriptor!, size: pointSize)
Niall Mccormack
  • 1,314
  • 13
  • 19