← 所有文章

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)和光学尺寸(opsz2。光学尺寸根据点大小自动调整;字重和宽度可通过SwiftUI的Font.WeightFont.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.plistUIAppFonts数组随应用打包的品牌字体)默认不会随Dynamic Type缩放。最简单的修复方法是Font.customrelativeTo:参数:

// 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+应用意味着什么

三点收获。

  1. 使用语义文本样式,而非手动调整的点大小。 .body.headline.title2等带有Dynamic Type、光学尺寸和符合平台规范的层级。手动调整的Font.system(size: 17)会破坏每个系统特性,并且当苹果调整默认梯度时会显得过时。

  2. 始终在自定义字体上传递relativeTo: 使用Font.custom(_, size: _, relativeTo: .body)打包的品牌字体能够加入Dynamic Type。不带它打包的品牌字体是逐用户的辅助功能退化,QA只有在最大文字尺寸下才会发现。

  3. 在辅助功能Dynamic Type尺寸下测试布局。 .accessibility3设置大约是Large默认的2倍。在标准尺寸下看起来不错的布局在辅助功能尺寸下经常破裂。修复方法是通过dynamicTypeSize环境进行布局级适配,而不是通过.dynamicTypeSize(...)约束选择退出。

完整的Apple Ecosystem系列:类型化的App IntentsMCP服务器路由问题Foundation Models运行时与工具LLM之分三个表面单一真相源模式两个MCP服务器苹果开发的钩子Live ActivitieswatchOS运行时SwiftUI内部RealityKit的空间心智模型SwiftData模式纪律Liquid Glass模式多平台发布平台矩阵Vision框架Symbol EffectsCore ML推理Writing Tools APISwift TestingPrivacy 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.plistUIAppFonts,通过Font.custom引用)。要从SwiftUI访问可变轴,请通过UIFontDescriptor.SymbolicTraits使用Font.custom底层的CTFont API,或者,如需完整的轴控制,则降到CTFontCreateCopyWithAttributes配合kCTFontVariationAttribute。从SwiftUI到可变轴的桥接比对系统字体更冗长;对于大多数应用,系统字体已能覆盖所需场景。

References


  1. Apple Developer:Fonts。系统字体家族概览,包括SF Pro、SF Pro Rounded、SF Mono、SF Compact和New York。 

  2. Apple Developer:Meet the expanded San Francisco font family(WWDC 2022 session 110381)。SF Pro Variable三轴设计(字重、宽度、光学尺寸)的介绍。 

  3. Apple Developer Documentation:Font.custom(_:size:relativeTo:)。让字体相对于所选文本样式加入Dynamic Type缩放的自定义字体初始化器。 

  4. Apple Developer Documentation:DynamicTypeSize。从.xSmall.accessibility5的十二值枚举,加上用于布局级适配的isAccessibilitySize断言。 

相关文章

Liquid Glass in SwiftUI: Three Patterns From Shipping Return on iOS 26

Apple's Liquid Glass is a one-line SwiftUI API. Three patterns from Return go beyond .glassEffect(): glass on text via C…

19 分钟阅读

Accessibility As Platform: Personal Voice, Live Speech, Eye Tracking, Music Haptics

Personal Voice, Live Speech, Eye Tracking, Music Haptics, Vocal Shortcuts: accessibility as platform features, not app r…

14 分钟阅读

The Design Engineer's Agent Stack

Design engineers need agent infrastructure that enforces visual consistency, typography discipline, color compliance, an…

14 分钟阅读