SF Pro:可变轴、光学尺寸与Dynamic Type契约
SF Pro是苹果自2015年iOS 9 / OS X El Capitan以来的系统字体,是一款具有三个轴(字重、宽度、光学尺寸)的可变字体,包含一个无衬线体(SF Pro)、一个圆角变体(SF Pro Rounded)、一个等宽体(SF Mono)、一个紧凑变体(SF Compact,用于watchOS)以及一个衬线体伴侣(New York)1。该字体围绕Dynamic Type契约设计:SwiftUI的十一种文本样式(.largeTitle、.title、.body、.callout、.footnote等)默认全部使用SF Pro,并且当用户在”设置”中更改首选文本大小时,它们会自动缩放。
大多数应用只使用这十一种样式中的一两种,除了默认行为外不再考虑Dynamic Type,也从不触及可变轴。结果是排版虽然能用,却没有讲出平台的语言。本文将走过系统字体家族、可变轴、十一种语义样式、Dynamic Type契约,以及自定义字体何时需要显式的缩放工作才能加入这套体系。
TL;DR
- SF Pro Variable暴露三个轴:字重(
wght)、宽度(wdth)和光学尺寸(opsz)2。光学尺寸根据点大小自动调整;字重和宽度可通过SwiftUI的Font.Weight和Font.Width访问。 - 系统字体家族包括SF Pro(默认)、SF Pro Rounded(友好的UI元素)、SF Mono(代码、技术UI)、SF Compact(watchOS、狭窄场景)以及New York(用于编辑阅读的衬线体伴侣)。
- SwiftUI的十一种文本样式自动支持Dynamic Type。
.body是安全的默认值;从.largeTitle到.caption2覆盖了平台的层级体系。 - 自定义字体默认不会随Dynamic Type缩放。使用
Font.custom("Name", size: 16, relativeTo: .body)可以让字体加入Dynamic Type缩放3。 @Environment(\.dynamicTypeSize)让视图能够根据当前文本大小调整布局;取值范围从.xSmall到.accessibility5(共12种尺寸)4。
系统字体家族
苹果提供了五个共同参与系统字体体验的家族。
SF Pro
每个苹果平台的默认字体。iPhone、iPad、Mac、Vision的正文、标题以及大多数UI都使用SF Pro。该字体支持广泛的语言(拉丁文、希腊文、西里尔文、阿拉伯文、希伯来文、天城文等),原生支持从右至左的版式,并包含为小型大写字母、备用数字(比例数字与表格数字)以及风格备选字形而设计的变体。
在SwiftUI中通过系统字体访问:
Text("Hello").font(.system(.body)) // SF Pro by default
Text("Hello").font(.system(.body, weight: .semibold)) // SF Pro Semibold
Text("Hello").font(.system(.body, design: .default)) // explicit SF Pro
SF Pro Rounded
为友好、亲切的UI而设计的圆角变体:Apple Watch复杂功能、健身圆环、地图方向标注的部分、查找以及精选的健康界面。圆角字形传达柔和感;选择它们是为了语调,不仅是为了视觉变化。
Text("Hello").font(.system(.body, design: .rounded))
SF Mono
用于代码、终端UI以及任何字符单元对齐至关重要场景的等宽字体家族。SF Mono是Xcode的默认字体、终端应用等宽设置的字体,以及任何请求.monospaced的SwiftUI视图所使用的字体。
Text("let x = 42").font(.system(.body, design: .monospaced))
SF Compact
一个更窄的字体家族,用于watchOS、tvOS焦点高亮,以及某些水平空间受限的Mac场景。更窄的字形在保留x-高度的同时减少了水平推进,使其适用于SF Pro会显得局促的Apple Watch表盘尺寸。
watchOS应用通过系统字体自动使用SF Compact;iOS应用通常不需要显式请求它。
New York
一个为编辑阅读而设计的衬线体家族。New York出现在用于长文阅读的图书应用、用于手写风格笔记的备忘录应用,以及SwiftUI中通过.serif设计访问。
Text("Long-form essay").font(.system(.body, design: .serif))
衬线体伴侣在应用UI中很少见。请有意识地使用它(阅读模式、引用段落、文章正文),而非作为默认。
三个可变轴
SF Pro Variable编码了三个轴,它们组合起来生成每个字形:
字重(wght)
九种命名字重:.ultraLight、.thin、.light、.regular、.medium、.semibold、.bold、.heavy、.black。可变字体在它们之间连续插值,但SwiftUI的API暴露的是命名值。
Text("Heading").font(.system(.title, weight: .semibold))
字重传达强调层级:.regular用于正文,.semibold或.bold用于标题,.medium用于活动的工具栏项,.light用于弱化的标签。语义样式(.headline、.subheadline)自带合理的字重默认值;只有当语义样式不是合适形态时,才使用显式字重。
宽度(wdth)
iOS 16+的API中有四种命名宽度:.compressed、.condensed、.standard、.expanded。宽度轴影响水平推进,但不改变字重或视觉特征。可用于紧凑UI(项目较多的导航栏)或视觉肌理(希望具有更强水平存在感的展示尺寸标题)。
宽度通过SwiftUI的.fontWidth(_:)视图修饰符(或在Font上链式调用.width(_:))应用:
Text("Compressed")
.font(.system(.title, weight: .bold))
.fontWidth(.compressed)
宽度很少是正文的合适轴;它在排版本身即是设计组成部分的展示尺寸中效果良好。
光学尺寸(opsz)
技术上最具雄心的轴。SF Pro原有的SF Text和SF Display变体如今编码为可变字体中的连续渐变。在20点以下的尺寸,系统应用SF Text的光学调整(更宽的字间距、稍重的笔画、更开放的字怀)。20点以上,SF Display接管(更紧的间距、更精炼的比例)。过渡是平滑的。
opsz轴是自动的。应用不显式处理它;系统读取请求的点大小并解析为正确的光学尺寸。这意味着:小UI尺寸的排版与同一字体在标题尺寸下具有不同的比例,而这是有意为之的设计。缺乏光学尺寸的自定义字体在某个尺寸下看起来不错,在其他尺寸下却显得不对;SF Pro的自动处理完全避开了这个陷阱。
十一种文本样式
SwiftUI的Font.TextStyle定义了十一种共同参与Dynamic Type的语义样式4:
| 样式 | 默认尺寸(Large) | 常见用途 |
|---|---|---|
.largeTitle |
34 pt | 顶级标题、英雄文本 |
.title |
28 pt | 章节标题 |
.title2 |
22 pt | 子章节标题 |
.title3 |
20 pt | 次要标题 |
.headline |
17 pt | 强调正文、列表行标题 |
.body |
17 pt | 默认正文 |
.callout |
16 pt | 辅助正文、上下文说明 |
.subheadline |
15 pt | 次级标题、元数据文本 |
.footnote |
13 pt | 小型辅助文本 |
.caption |
12 pt | 图片说明、小字 |
.caption2 |
11 pt | 最小可读文本 |
“默认尺寸”列显示的是用户在”Large” Dynamic Type设置(系统默认)下的尺寸。每种样式会随用户调整首选文本大小而放大或缩小;相对层级保持完整。
正确的采用方式是直接使用语义样式:
VStack(alignment: .leading) {
Text("Title").font(.title)
Text("Subtitle").font(.subheadline).foregroundStyle(.secondary)
Text("Body content here.").font(.body)
}
层级在每个Dynamic Type设置下都得以保留,遵守了苹果HIG的约定,排版无需逐个应用编码即可响应用户的辅助功能偏好。
Dynamic Type契约
Dynamic Type是用户在”设置 > 辅助功能 > 显示与文字大小 > 更大字体”中控制的文字大小设置。该值通过环境以DynamicTypeSize的形式流转,包含从.xSmall到.accessibility5的十二个值4:
- 标准尺寸:
.xSmall、.small、.medium、.large(默认)、.xLarge、.xxLarge、.xxxLarge - 辅助功能尺寸:
.accessibility1、.accessibility2、.accessibility3、.accessibility4、.accessibility5
使用语义文本样式的应用免费获得缩放。希望根据当前尺寸调整布局的应用则读取环境:
struct AdaptiveLayout: View {
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
if dynamicTypeSize.isAccessibilitySize {
VStack { content } // stack vertically at accessibility sizes
} else {
HStack { content } // horizontal at standard sizes
}
}
}
isAccessibilitySize属性涵盖五个辅助功能尺寸(在这些尺寸下文字足够大,水平布局往往会破裂)。对于任何依赖文本水平容纳的布局,这都是正确的模式。
应用可以通过.dynamicTypeSize(_:)修饰符约束支持范围:
ContentView()
.dynamicTypeSize(.large ... .accessibility3)
该约束会裁剪修饰子树的Dynamic Type设置。仅当某个视图的布局确实无法容纳完整范围时才使用它;正确的默认是支持每种尺寸并相应调整布局。
自定义字体与Dynamic Type
自定义字体(通过Info.plist的UIAppFonts数组随应用打包的品牌字体)默认不会随Dynamic Type缩放。最简单的修复方法是Font.custom的relativeTo:参数:
// Doesn't scale with Dynamic Type
Text("Brand").font(.custom("MyBrandFont", size: 16))
// Scales with Dynamic Type relative to body
Text("Brand").font(.custom("MyBrandFont", size: 16, relativeTo: .body))
relativeTo:参数告诉SwiftUI使用body样式的缩放曲线来缩放自定义字体。在用户的”Large”设置下,字体大小是请求的16pt;在更大的设置下,SwiftUI应用body样式所使用的相同倍数。
如需更复杂的缩放(不同尺寸下的不同曲线、自定义光学处理),请直接使用UIKit的UIFontMetrics。该模式更冗长,但支持自定义字体常常需要的逐尺寸调整。
排版何时失败
值得点名的三种失败模式:
到处使用固定尺寸的自定义字体。 最常见的iOS应用辅助功能失败:使用Font.custom("BrandFont", size: 16)(无relativeTo:)打包的品牌字体完全忽略Dynamic Type。使用辅助功能文字大小的用户看到的品牌文字停留在16pt,而系统文字缩放到28pt+;视觉层级反转。修复方法是在每次使用自定义字体时都加上relativeTo:,并在最大Dynamic Type设置下通过AccessibilityInspector审计。
为了强调而硬编码字重。 一个样式为.font(.body).fontWeight(.bold)的副标题很脆弱:在辅助功能尺寸下,加粗的body与本就很大的body几乎难以区分。语义化的.headline样式在整个Dynamic Type范围内都能正确处理强调;请用它替代body+bold。
在辅助功能尺寸下破裂的布局。 文本+图标+文本的水平堆栈在.accessibility3下溢出,是Dynamic Type暴露的布局缺陷。修复方法是上述dynamicTypeSize.isAccessibilitySize的自适应布局模式;测试方法是在QA时以最大Dynamic Type运行应用,而非只测试默认尺寸。
这种模式对iOS 26+应用意味着什么
三点收获。
-
使用语义文本样式,而非手动调整的点大小。
.body、.headline、.title2等带有Dynamic Type、光学尺寸和符合平台规范的层级。手动调整的Font.system(size: 17)会破坏每个系统特性,并且当苹果调整默认梯度时会显得过时。 -
始终在自定义字体上传递
relativeTo:。 使用Font.custom(_, size: _, relativeTo: .body)打包的品牌字体能够加入Dynamic Type。不带它打包的品牌字体是逐用户的辅助功能退化,QA只有在最大文字尺寸下才会发现。 -
在辅助功能Dynamic Type尺寸下测试布局。
.accessibility3设置大约是Large默认的2倍。在标准尺寸下看起来不错的布局在辅助功能尺寸下经常破裂。修复方法是通过dynamicTypeSize环境进行布局级适配,而不是通过.dynamicTypeSize(...)约束选择退出。
完整的Apple Ecosystem系列:类型化的App Intents;MCP服务器;路由问题;Foundation Models;运行时与工具LLM之分;三个表面;单一真相源模式;两个MCP服务器;苹果开发的钩子;Live Activities;watchOS运行时;SwiftUI内部;RealityKit的空间心智模型;SwiftData模式纪律;Liquid Glass模式;多平台发布;平台矩阵;Vision框架;Symbol Effects;Core ML推理;Writing Tools API;Swift Testing;Privacy Manifest;作为平台的辅助功能;我拒绝撰写的内容。中心枢纽位于Apple Ecosystem系列。如需更广泛的iOS与AI agent上下文,请参阅iOS Agent Development指南。
FAQ
SF Pro与SF Pro Display / SF Pro Text有何区别?
从iOS 9(SF首次作为系统字体发布时)到iOS 16,SF以两个独立字体的形式发布:SF Text用于20pt以下的尺寸,SF Display用于20pt及以上的尺寸,每个都为其尺寸范围手动调整了字间距和笔画粗细。SF Pro Variable将相同的Text/Display分割整合为一个连续的光学尺寸轴(opsz)。两种字体不再独立;可变字体根据请求的点大小自动处理过渡。
如何在正文中获得等宽数字?
在SwiftUI中使用.monospacedDigit():
Text("\(score)").font(.body).monospacedDigit()
该修饰符将正文字体的比例数字替换为等宽数字,同时保持文本其余部分为比例宽度。请用于任何数字必须跨行对齐的UI(计时器、计分板、余额显示)。
我应该在所有UI中使用SF Pro Rounded吗?
不应该。SF Pro Rounded带有一种语调(友好、亲切),适合某些场景而非所有场景。Watch应用的复杂功能、iPhone的电话拨号键盘以及某些健康应用界面会使用它。生产力应用、银行应用或开发者工具通常不应使用。请有意识地选择.rounded,而非作为默认。
iPhone应用合适的Dynamic Type范围是什么?
默认支持从.xSmall到.accessibility5的每种尺寸。辅助功能尺寸(.accessibility1到.accessibility5)是低视力、运动障碍或其他辅助功能需求用户使用iPhone的方式。通过.dynamicTypeSize(...)约束选择退出的应用会让这些用户失望。正确的做法是布局适配(isAccessibilitySize模式),而非选择退出尺寸范围。
我可以将自定义可变字体随应用打包吗?
可以。可变字体像任何其他自定义字体一样打包(添加到Info.plist的UIAppFonts,通过Font.custom引用)。要从SwiftUI访问可变轴,请通过UIFontDescriptor.SymbolicTraits使用Font.custom底层的CTFont API,或者,如需完整的轴控制,则降到CTFontCreateCopyWithAttributes配合kCTFontVariationAttribute。从SwiftUI到可变轴的桥接比对系统字体更冗长;对于大多数应用,系统字体已能覆盖所需场景。
References
-
Apple Developer:Fonts。系统字体家族概览,包括SF Pro、SF Pro Rounded、SF Mono、SF Compact和New York。 ↩
-
Apple Developer:Meet the expanded San Francisco font family(WWDC 2022 session 110381)。SF Pro Variable三轴设计(字重、宽度、光学尺寸)的介绍。 ↩
-
Apple Developer Documentation:
Font.custom(_:size:relativeTo:)。让字体相对于所选文本样式加入Dynamic Type缩放的自定义字体初始化器。 ↩ -
Apple Developer Documentation:
DynamicTypeSize。从.xSmall到.accessibility5的十二值枚举,加上用于布局级适配的isAccessibilitySize断言。 ↩↩↩