SFPro Font Features


It’s been few years already since Apple ditched Helvetica in favour of their proprietary SanFrancisco font. While SFPro font family comes with plethora of features to satisfy even obscure typographic needs, actually finding and using these features can require parsing trough lengthy documentation.

Feature Keys

Font features in AAT (Apple Advanced Typography) are enabled using a pair of keys. type for selecting a some feature and selector for enabling it. A non-exhaustive list of these features can be found in the Font Feature Registry

To make working with keys a bit simpler we can create a small abstraction. Feature struct will let us name the key pairs as static variables and enjoy autocomplete.

struct Feature: Hashable {
	private let type: Int
	private let selector: Int
	var setting: [UIFontDescriptor.FeatureKey : Int] { 
		[.featureIdentifier: type, .typeIdentifier: selector]
	}
}

⚠️ The naming above is intentional.
The .featureIdentifier is set with .k(...)Type keys
while .typeIdentifier uses .k(...)Selector.

The list of possible features is pretty long and some of them can only be used exclusively. Here are most of the features, presented in the WWDC session. As you can see the keys can get pretty verbose.

static let straightSixAndNine = 
	Feature(type: kStylisticAlternativesType, selector: kStylisticAltOneOnSelector)
static let openFour = 
	Feature(type: kStylisticAlternativesType, selector: kStylisticAltTwoOnSelector)
static let alignedColon = 
	Feature(type: kStylisticAlternativesType, selector: kStylisticAltThreeOnSelector)
static let highLegibility = 
	Feature(type: kStylisticAlternativesType, selector: kStylisticAltSixOnSelector)
static let oneStoreyA = 
	Feature(type: kStylisticAlternativesType, selector: kStylisticAltSixOnSelector)
static let monospacedNumbers = 
	Feature(type: kNumberSpacingType, selector: kMonospacedNumbersSelector)
static let diagonalFractions = 
	Feature(type: kFractionsType, selector: kDiagonalFractionsSelector)
static let loverCaseSmallCaps = 
	Feature(type: kLowerCaseType, selector: kLowerCaseSmallCapsSelector)
static let upperCaseSmallCaps = 
	Feature(type: kUpperCaseType, selector: kUpperCaseSmallCapsSelector)

Caching

When required frequently enough, constructing fonts with typographic features can be considered an expensive operation. Therefore we can intruduce a simple caching mechanism using a dictionary. This dictionary is keyed by a hashable struct, which represents the required function parameters.

private static var cache = [Key: UIFont]()
private struct Key: Hashable {
	let size: CGFloat
	let weight: Weight
	let features: [Feature]
	let italic: Bool
}

Usage

With setup work out of the way, the font can now be constructed!

static func systemFont(
	ofSize size: CGFloat,
	weight: Weight = .regular,
	features: [Feature] = [],
	italic: Bool = false
) -> UIFont {
	let key = Key(size: size, weight: weight, features: features, italic: italic)
	if let font = cache[key] { return font }
	let descriptor = UIFont.systemFont(ofSize: size, weight: weight).fontDescriptor
	let font = UIFont(
		descriptor: descriptor
			.addingAttributes([.featureSettings: features.map { $0.setting }])
			.withSymbolicTraits(descriptor
				.symbolicTraits
				.union(italic ? .traitExpanded : .init(rawValue: 0))
			) ?? descriptor,
		size: size
	)
	cache[key] = font
	return font
}

For Designers

sketch