关于 axiom-typography-ref
面向 Apple 平台界面设计与开发者的排版参考技能,汇总系统字体、文本样式、Dynamic Type、行距、字距及国际化要点,并提供 SwiftUI 和 UIKit 示例。
主要功能
- 01介绍 Apple 系统字体系列
- 02说明文本样式与 Dynamic Type
- 03调整字距、行距和行高
- 04处理 AttributedString 排版
- 05涵盖多语言文本布局
适用场景
- 01为界面选择合适的字体样式
- 02让自定义字体随 Dynamic Type 缩放
- 03微调段落行高和字距
- 04排查富文本样式被覆盖的问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-typography-ref安装后在 Claude Code 中输入/axiom-typography-ref确认已加载。
详细介绍
字体排印参考
Apple 平台字体排印完整参考,涵盖 San Francisco 字体系统、文本样式、动态字体大小、字距、行距,以及截至 iOS 26 的国际化。
San Francisco 字体系统
字体系列
SF Pro 和 SF Pro Rounded(iOS、iPadOS、macOS、tvOS)
- 大多数 UI 元素使用的主要系统字体
- 圆角变体适用于友好、亲切的界面(例如“提醒事项”App)
SF Compact 和 SF Compact Rounded(watchOS、窄栏)
- 针对受限空间和较小字号进行优化
- watchOS 默认系统字体
SF Mono(代码环境、等宽文本)
- 适用于代码编辑器和技术内容的等宽字体
- 字符宽度一致,便于对齐
New York(系统衬线字体)
- 适用于编辑类内容的衬线字体替代方案
- 与 SF Pro 一样支持文本样式
可变字体轴
字重轴(9 种字重)
- Ultralight、Thin、Light、Regular、Medium、Semibold、Bold、Heavy、Black
- 通过可变字体实现连续的字重范围
- 小字号时避免使用较细字重(会影响易读性)
字宽轴(WWDC 2022)
- Condensed — 最窄
- Compressed — 较窄
- Regular — 标准宽度(默认)
- Expanded — 较宽
通过以下方式访问:
// iOS/macOS
let descriptor = UIFontDescriptor(fontAttributes: [
.family: "SF Pro",
kCTFontWidthTrait: 1.0 // 1.0 = Expanded
])
SF Arabic(WWDC 2022)
- 阿拉伯语文本采用与 SF Pro 一致的设计风格
- 正确支持从右到左的排版
光学字号
可变字体会根据字号自动调整光学尺寸:
- Text 变体(< 20pt)— 字距更大、笔画更粗壮
- Display 变体(≥ 20pt)— 字距更紧凑、细节更精致
- 使用可变 SF Pro 时可平滑过渡(17-28pt)
来自 WWDC 2020:
“TextKit 2 抽象了字形处理,从而为国际文本提供一致的体验。”
文本样式与动态字体大小
系统文本样式
| 文本样式 | 默认字号(iOS) | 用途 |
|---|---|---|
.largeTitle | 34pt | 主要页面标题 |
.title | 28pt | 次级标题 |
.title2 | 22pt | 第三级标题 |
.title3 | 20pt | 第四级标题 |
.headline | 17pt(Semibold) | 强调正文 |
.body | 17pt | 主要正文 |
.callout | 16pt | 次要正文 |
.subheadline | 15pt | 第三级正文 |
.footnote | 13pt | 脚注、说明文字 |
.caption | 12pt | 小号注释 |
.caption2 | 11pt | 最小号注释 |
字号指南
- 避免将
.caption2用于需要易读的内容 — 11pt 适合时间戳和元数据注释,但对于正文或用户需要阅读的标签来说太小。易读内容的最小字号建议使用.caption或.footnote。
强调文本样式
应用 .bold 符号特征以获取强调变体:
// UIKit
let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title1)
let boldDescriptor = descriptor.withSymbolicTraits(.traitBold)!
let font = UIFont(descriptor: boldDescriptor, size: 0)
// SwiftUI
Text("Bold Title")
.font(.title.bold())
各文本样式的实际字重:
- 某些样式映射为 medium
- 其他样式映射为 semibold、bold 或 heavy
- 具体取决于语义层级
行距变体
紧凑行距(iOS 行高减少 2pt,watchOS 减少 1pt):
// UIKit
let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
let tightDescriptor = descriptor.withSymbolicTraits(.traitTightLeading)!
// SwiftUI
Text("Compact text")
.font(.body.leading(.tight))
宽松行距(iOS 行高增加 2pt,watchOS 增加 1pt):
// SwiftUI
Text("Spacious paragraph")
.font(.body.leading(.loose))
动态字体大小
自动缩放(iOS): 文本样式会根据用户在“设置”→“显示与亮度”→“文字大小”中的偏好自动缩放。
支持动态字体大小的自定义字体:
// UIKit - UIFontMetrics
let customFont = UIFont(name: "Avenir-Medium", size: 34)!
let bodyMetrics = UIFontMetrics(forTextStyle: .body)
let scaledFont = bodyMetrics.scaledFont(for: customFont)
// Also scale constants
let spacing = bodyMetrics.scaledValue(for: 20.0)
// SwiftUI - .font(.custom(_:relativeTo:))
Text("Custom scaled text")
.font(.custom("Avenir-Medium", size: 34, relativeTo: .body))
// @ScaledMetric for values
@ScaledMetric(relativeTo: .body) var padding: CGFloat = 20
平台差异
macOS
- AppKit 不支持动态字体大小
- 文本样式字号针对 macOS 控件尺寸进行了优化
- Catalyst App 使用 iOS 字号 × 77%(旧版),或 macOS 优化字号(“为 Mac 优化界面”)
watchOS
- 较小的文本样式针对表盘进行了优化
- 紧凑显示屏默认使用紧凑行距
visionOS
- 系统字体的工作方式与 iOS 完全相同
- 支持动态字体大小
字距与行距
字距(字母间距)
字距用于调整字母之间的间距,是实现光学字号效果的关键。
按字号区分的字距表:
SF Pro 包含随字号变化的字距值,以保持最佳间距:
- 较大字号:字距更紧
- 较小字号:字距更宽
Apple Design Resources 中的示例:
- 34pt(largeTitle):字距 +0.016
- 17pt(body):字距 +0.008
- 11pt(caption2):字距 +0.06
紧凑字距 API(用于适配文本):
// UIKit
textView.allowsDefaultTightening(for: .byTruncatingTail)
// SwiftUI
Text("Long text that needs to fit")
.lineLimit(1)
.minimumScaleFactor(0.5) // Allows tight tracking
手动设置字距:
// UIKit
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.preferredFont(forTextStyle: .body),
.kern: 2.0 // 2pt tracking
]
// SwiftUI
Text("Tracked text")
.tracking(2.0)
.kerning(2.0) // Alternative API
重要: 为确保语义正确,请使用 .tracking(),而不是 .kerning() API。必要时,字距会禁用连字;字偶间距则不会。
行距(行间距)
默认行高: 根据字体内置度量计算(升部 + 降部 + 行间距)。
针对语言的调整: iOS 17+ 会自动增加具有较高升部/降部的文字系统的行高:
- 阿拉伯语
- 泰语、老挝语
- 印地语、孟加拉语、泰卢固语
摘自 WWDC 2023:
“针对高度可变的文字系统自动调整行高”
手动设置行距:
// UIKit
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 8.0 // 8pt additional space
// SwiftUI (iOS 13+)
Text("Custom spacing")
.lineSpacing(8.0)
行高(iOS 26+):
.lineHeight() 直接设置基线到基线的距离,比 .lineSpacing()(测量从底部到顶部的距离)更直观。
// Presets
Text("Open layout").lineHeight(.loose)
Text("Compact layout").lineHeight(.tight)
// Precise control
Text("Scaled").lineHeight(.multiple(factor: 1.5))
Text("Fixed").lineHeight(.exact(points: 30)) // Does NOT scale with Dynamic Type
对于设置了样式的字符串,也可以使用 AttributedString.lineHeight。完整 API 详情请参阅 axiom-swiftui-26-ref。
第三方字体字距
iOS 18 新增: 字体供应商可以使用 STAT 表和 CTFont 光学字号属性,在自定义字体中嵌入字距表。
let attributes: [String: Any] = [
kCTFontOpticalSizeAttribute as String: pointSize
]
let descriptor = CTFontDescriptorCreateWithAttributes(attributes as CFDictionary)
let font = CTFontCreateWithFontDescriptor(descriptor, pointSize, nil)
SwiftUI AttributedString 排版
字体环境交互
关键模式 使用 AttributedString 搭配 SwiftUI 的 Text 时,如果字体来自环境而非属性内容,段落样式(如 lineHeightMultiple)可能会丢失。
摘自 WWDC 2025-280:
“对于任何值为 nil 的 AttributedStringKeys,TextEditor 都会用根据环境计算出的默认值替代。”
同样的原则也适用于 Text——当 AttributedString 未指定字体时,SwiftUI 会应用环境字体,这可能导致它重建文本片段,并丢弃或规范化段落样式细节。
问题
// ❌ WRONG - .font() modifier can override and drop paragraph styles
var s = AttributedString(longString)
// Set paragraph style
var p = AttributedString.ParagraphStyle()
p.lineHeightMultiple = 0.92
s.paragraphStyle = p
// ⚠️ No font set in AttributedString
Text(s)
.font(.body) // ⚠️ May rebuild runs, lose lineHeightMultiple
失败原因:
AttributedString未设置字体属性(值为nil)- SwiftUI 的
.font(.body)修饰符会告知它“对整个文本片段使用此字体” - SwiftUI 使用环境字体重建文本片段
- 重建过程中,段落样式会被丢弃或规范化
解决方案
需要精细控制时,请将排版信息保留在 AttributedString 中:
// ✅ CORRECT - Font in AttributedString, no environment override
var s = AttributedString(longString)
// Set font INSIDE the attributed content
s.font = .system(.body) // ✅ Typography inside AttributedString
// Set paragraph style
var p = AttributedString.ParagraphStyle()
p.lineHeightMultiple = 0.92
s.paragraphStyle = p
Text(s) // ✅ No .font() modifier
有效原因:
- 字体属于属性内容的一部分(而非
nil) - 不会受到
.font()修饰符的环境覆盖 - SwiftUI 会保留字体和段落样式
- 文本片段及其所有属性都会保持完整
何时使用哪种方式
在 AttributedString 中使用字体(精细控制)
var s = AttributedString("Carefully styled text")
s.font = .system(.body)
var p = AttributedString.ParagraphStyle()
p.lineHeightMultiple = 0.92
p.alignment = .leading
s.paragraphStyle = p
Text(s) // No modifier
适用场景:
- 需要精确设置段落样式(行高、对齐方式)
- 在一个字符串中混用多种字体
- 内容将在
Text和TextEditor中显示 - 保留富文本编辑器中的精确格式
使用 .font() 修饰符(全局覆盖)
Text("Simple text")
.font(.body)
.lineSpacing(4.0) // SwiftUI-level spacing
适用场景:
- 不带段落样式的简单文本
- 需要动态字体自动缩放
- 需要 SwiftUI 的语义字体行为(深色模式、辅助功能)
- 有意覆盖 AttributedString 字体
在一个字符串中使用多种字体
var s = AttributedString("Title")
s.font = .system(.title).bold()
var body = AttributedString(" and body text")
body.font = .system(.body)
s.append(body)
Text(s) // ✅ No .font() modifier preserves both fonts
常见误区:顺序无关紧要
// ❌ WRONG mental model: "Create AttributedString first"
var s = AttributedString(text)
var p = AttributedString.ParagraphStyle()
p.lineHeightMultiple = 0.92
s.paragraphStyle = p
s.font = .system(.body) // ⚠️ Setting font last doesn't help if you use .font() modifier
Text(s).font(.body) // Still breaks!
问题不在于你何时在 AttributedString 中设置字体,而在于属性内容是否包含自己的字体属性,而不是依赖 SwiftUI 的 .font(...) 环境。
验证清单
使用 AttributedString 设置段落样式时:
- 在
AttributedString中设置字体(而不是nil) - 不对
Text视图使用.font()修饰符(除非有意覆盖) - 在字体之前或之后设置段落样式(顺序无关)
- 使用实际内容进行测试,确认行高和对齐方式得以保留
国际化
双向文本
复杂文字示例(摘自 WWDC 2021):
卡纳达语单词“October”:
- 字符索引 4 处的分裂元音会对应 2 个字形
- 连字应用前会对字形重新排序
- 字形索引 ≠ 字符索引
这就是 TextKit 2 使用 NSTextLocation 而非整数索引的原因。
希伯来语/阿拉伯语选择:
由于从右向左的排版,单个视觉选区在 AttributedString 中会对应多个 NSRange。
换行
语言感知(iOS 17+):
- 中文、日文、韩文:在语义边界处换行
- 德语:避免拆分复合词
- 英语:优先在连字符处换行
均匀换行(TextKit 2): 两端对齐的段落采用改进的换行算法:
- 减少行间距过大的情况
- 让单词间距更均匀
- TextKit 2 中自动启用
防止文本裁切
最佳实践:
- 使用动态字体(自动调整)
- 在 SwiftUI 中设置
.lineLimit(nil)或.lineLimit(2...5) - 对受限的单行文本使用
.minimumScaleFactor() - 使用较大的辅助功能字号进行测试
CSS 与 Web 排版
系统 UI 字体系列:
font-family: system-ui; /* SF Pro */
font-family: ui-rounded; /* SF Pro Rounded */
font-family: ui-serif; /* New York */
font-family: ui-monospace; /* SF Mono */
旧版:
font-family: -apple-system; /* deprecated, use system-ui */
代码示例
强调的大标题(SwiftUI)
Text("Recipe Editor")
.font(.largeTitle.bold()) // Emphasized variant
自定义字体 + 动态字体(UIKit)
let customFont = UIFont(name: "Avenir-Medium", size: 17)!
let metrics = UIFontMetrics(forTextStyle: .body)
label.font = metrics.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
圆角设计(UIKit)
let descriptor = UIFontDescriptor
.preferredFontDescriptor(withTextStyle: .largeTitle)
.withDesign(.rounded)!
let font = UIFont(descriptor: descriptor, size: 0)
圆角设计(SwiftUI)
Text("Today")
.font(.largeTitle.bold())
.fontDesign(.rounded)
ScaledMetric(SwiftUI)
struct RecipeView: View {
@ScaledMetric(relativeTo: .body) var padding: CGFloat = 20
var body: some View {
Text("Recipe")
.padding(padding) // Scales with Dynamic Type
}
}
资源
WWDC:2020-10175、2022-110381、2023-10058
文档:/uikit/uifontdescriptor、/uikit/uifontmetrics、/swiftui/font