Activating OpenType Features in iOS
Inter is an awesome typeface. It is free, open-source, and on top of looking great by default, it provides many OpenType features that are really easy to use on a website. The alternate digits feature is one of them. Just add the following to a CSS style and you’re done.
font-feature-settings: "ss01";
How can we benefit from the same optional features with UIKit? This post will recap my journey to activate the alternate digits feature on an iOS app.
Adding a custom font and using it
Adding a custom font to your app is now pretty easy, compared to the old days. From there, the most obvious next step is to run a piece of code that I include in every project that use custom fonts. It simply prints a list of the font names that are available from your app.
public extension UIFont {
static func allFontNames() -> [String] {
familyNames.sorted()
.map { UIFont.fontNames(forFamilyName: $0).sorted() }
.flatMap { $0 }
}
}
Listing features of a font
Activating features with UIKit is described in the Activating Font Features section of the Text Programming Guide for iOS. The code that is presented there relies on Objective-C Core Text that you can wrap in modern Swift.
To list the features that are supported by a font, you can use the following code:
extension UIFont {
typealias FontFeatureInfo = [String: Any]
func listFeatures() {
guard let fontFeatures = CTFontCopyFeatures(self) as? [FontFeatureInfo] else {
debugPrint("Could not copy font features.")
return
}
fontFeatures.forEach { fontFeatureInfo in
print(fontFeatureInfo)
}
}
}
It is hard to predict how large the output of this command will be and really depends on the font. For the Inter font, the output looks pretty intense. It is not the most pleasant prose to read for humans, but scanning for the features we want will be pretty straightforward.
In my context, the part I was interested in was the following:
["CTFeatureTypeName": Alternative Stylistic Sets, "CTFeatureTypeIdentifier": 35, "CTFeatureTypeSelectors": <__NSArrayM 0x600002179f20>(
{
CTFeatureSelectorIdentifier = 2;
CTFeatureSelectorName = "Open digits";
},
To activate the Open digits feature, I had to use the feature type identifier 35, with the feature selector identifier 2.
Enabling the font features you want
So far, the code I showed was to get your app in the starting blocks. It helps you to prepare for the big stage moment: actually enabling the font features.
So I wrapped these 35 and 2 magic numbers into something more meaningful: a simple struct trying to make sense of Apple’s documentation1. It is pretty basic, aimed to be built on in the future, and I made use of this incredible Font Feature Registry resource2 to find inspiration for an adequate naming:
private enum FeatureTypeSelectorPair {
typealias PairDescriptor = (Int, Int)
static let stylisticAlternativesStylisticAltOne: PairDescriptor = (35, 2)
static func attributeValue(for pairDescriptor: PairDescriptor) -> Any {
[
UIFontDescriptor.FeatureKey.featureIdentifier: pairDescriptor.0,
UIFontDescriptor.FeatureKey.typeIdentifier: pairDescriptor.1,
]
}
}
Once there, I was able to instanciate the UIFont
including the option I wanted
like so:
func createFontWithOptions(fontName: String, size: CGFloat) -> UIFont {
let resultFont = UIFontDescriptor(name: fontName, size: size)
.addingAttributes([
.featureSettings: [
FeatureTypeSelectorPair.attributeValue(
for: FeatureTypeSelectorPair.stylisticAlternativesStylisticAltOne
),
],
])
// 0.0 means the size from the descriptor will prevail.
return UIFont(descriptor: resultFont, size: 0.0)
}
The outcome: an alternative digits style
🎉 Tada! I was done. Here is what it looks like in the end: a label with the default Inter Bold font appears first, and the label with the Inter Bold with the alternate digits feature enabled appears last.
Check out ⛹️ the playground to run the code in Xcode.
-
⚠️ Note: the documentation of
UIFontDescriptor.FeatureKey
was one of the most confusing I met. It said (1) thatfeatureIdentifier
was a key for identifying a font feature type such as ligature or character shape and (2)typeIdentifier
was a key for identifying the font feature selector. SofeatureIdentifier
refers toCTFeatureTypeIdentifier
whiletypeIdentifier
refers toCTFeatureSelectorIdentifier
. Of course, I had it switched the wrong way the first time I ran the code. Confusing you said? 🤯 ↩ -
I never considered myself an expert in typefaces or fonts design and abilities but this resource was so humbling that I definitely decreased the perception of my own level in that skill by quite some orders of magnitude 😂 ↩