The details of UI typography
Description: Learn how to achieve exceptional typography in your app’s user interface that enhances legibility, accessibility, and consistency across Apple platforms. Get up to speed on the latest advancements to the San Francisco font family including the move to variable fonts for accommodating optical sizes and weights. We’ll also share tips about how to get the most out of systems fonts, support dynamic type with custom fonts.
For a refresher on the principles behind the San Francisco font family, catch up on “Introducing the New System Fonts” from WWDC15.
Optical Sizes
- SF font was designed to provide two separate versions depending on the current size set, for fonts smaller than 20 points it used
SF Text
while fonts at 20 points or higher used SF Display
- The difference between these two fonts lies in different aspects, spacing and vertical proportion for example
Variable Fonts
- Introduced in 2016, variable fonts lets you use one font that adapts to different text point sizes dynamically
- This year, Apple has moved away from multiple fonts versions to a single
SFPro.ttf
font which can change how it looks in many different aspects according to its font size - The following APIs handle optical sizes automatically for system fonts (as well as custom fonts with optical size access):
preferredFont(forTextStyle:)
systemFont(ofSize:)
systemFont(ofSize:weight:)
CTFontCreateUIFontForLanguage()
Tracking and Leading
- Tracking is the action of adding spaces between glyphs in text layout (equally across the whole word). It’s different than Kerning which is a micro-correction in spacing that’s only applied to certain pairs
| |
---|
Tracking | Kerning |
- Previously,
SFText
and SFDisplay
had different tracking values, because smaller fonts usually requires more tracking to make it easier to read text
| |
---|
SF Text | SF Display |
- Now, SF font doesn’t have a hard breakpoint at 20pts for tracking, instead it varies smoothly between 17 and 28 points
- System fonts have a pre defined tracking table that map each font size to the correct tracking
- Also, fonts provide multiple tracking tables (for tightening for ex.) that is used when the available space is too tight by assigning:
label.allowsDefaultTighteningForTruncation = true
textField.allowsDefaultTighteningForTruncation = true
Text("hamburgefonstiv").allowsTightening(true)
- Platforms now recognizes different tracking for custom fonts containing both
track
and STAT
tables - By applying
kCTFontOpticalSizeAttribute
you can enable this behavior for old OSs as well
- Leading is the added space between lines vertically
- The system adds extra leading space for some languages that requires it (for example Arabic fonts)
Text Styles and Dynamic Type
- Text styles are a set of predefined combinations of weight, size and leading values
- Here is how you can get a font of a certain style with emphasized weight:
let descriptor = NSFontDescriptor
.preferredFontDescriptor(forTextStyle: .title1)
.withSymbolicTraits(.bold)
let emphasizedBodyFont = NSFont(descriptor: descriptor, size: 0)
if let descriptor = UIFontDescriptor
.preferredFontDescriptor(withTextStyle: .body)
.withSymbolicTraits(.traitBold) {
let emphasizedBodyFont = UIFont(descriptor: descriptor, size: 0)
}
let emphasizedFootnoteFont = Font.footnote.bold()
- You can also use tight and loose leading setting to allow more information to be displayed on screen when you have a dense screen, or allow for more room to make easier to read a large content
- Tight and loose leading setting applied on platforms are as follows:
Platform | Standard Leading | Tight Leading | Loose Leading |
---|
iOS | 0 | - 2 pt | + 2 pt |
macOS | 0 | - 2 pt | + 2 pt |
watchOS | 0 | - 1 pt | + 1 pt |
- Here is an example of how you can use tight/loose leading APIs:
let descriptor = NSFontDescriptor.preferredFontDescriptor(forTextStyle: .headline)
.withSymbolicTraits(.tightLeading)
let tightLeadingFont = NSFont(descriptor: descriptor, size: 0)
if let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title1)
.withSymbolicTraits(.traitTightLeading) {
let tightLeadingFont = UIFont(descriptor: descriptor, size: 0)
}
let tightLeadingFootnoteFont = Font.footnote.leading(.tight)
- You can also mimic the design of rounded fonts in the Reminders app:
let descriptor = NSFontDescriptor.preferredFontDescriptor(forTextStyle: .body)
.withDesign(.rounded)
let roundedBodyFont = NSFont(descriptor: descriptor, size: 0)
if let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
.withDesign(.rounded) {
let roundedBodyFont = UIFont(descriptor: descriptor, size: 0)
}
let roundedBodyFont = Font.system(.body, design: .rounded)
- Access to system fonts through CSS has changed a little bit with some additions, WebKit now supports following generic font families:
font-family: system-ui;
font-family: ui-rounded;
font-family: ui-serif;
font-family: ui-monospace;
- Text styles API now support macOS
- AppKit API defines full range of text styles as in iOS
- Font sizes are optimized to match recommended app text size (for this choose
Optimize Interface for Mac
in Xcode project editor instead of Scale Interface to Match iPad
) - No Dynamic Type support
- Dynamic Type allows the font to scale its size according to the user’s preferred text sizes set in settings
- Different text styles scales differently to Dynamic Size, so you need to adopt such behavior in your custom font (if you’re using one)
- In UIKit, you can do that by using
UIFontMetric
class:
if let customFont = UIFont(name: "Charter-Roman", size: 17) {
let bodyMetrics = UIFontMetrics(forTextStyle: .body)
let customFontScaledLikeBody = bodyMetrics.scaledFont(for: customFont)
label.font = customFontScaledLikeBody
label.adjustsFontForContentSizeCategory = true
let scaledValue = bodyMetrics.scaledValue(for: 10)
}
- New in SwiftUI, you can set your custom font to be relative to a system text style for it to scale correctly with dynamic type (this is not needed in iOS 14 as all fonts are relative to
.body
text style as a default behavior) - You can also use the new
@ScaledMetric
property wrapper to make any value scales with Dynamic Types relative to a certain text style:
struct ContentView: View {
let prose = "Apple provides two type families you can use in your iOS apps. San Francisco (SF). San Francisco is a sans serif type family that includes SF Pro, SF Pro Rounded, SF Mono, SF Compact, and SF Compact Rounded."
@ScaledMetric(relativeTo: .body) var padding: CGFloat = 20
var body: some View {
VStack {
Text("Typography")
.font(.custom("Avenir-Medium", size: 34, relativeTo: .title))
Text(prose)
.font(.custom("Charter-Roman", size: 17))
.padding(padding)
}
}
}
You can opt-out of default relative font behavior in iOS 14 by using the parameter fixedSize
:
Text("Fixed").font(.custom("Courier", fixedSize: 17))
Takeaways
- Try using system fonts such as
SF Pro
, SF Pro Rounded
, SF Mono
, and New York
- Using text styles helps create typographic hierarchy
- Consider supporting Dynamic Type with custom fonts
- Only override system behavior in exceptional cases
- Custom tracking should be size specific