axiom-typography-ref by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-typography-ref适用于 Apple 平台的完整排版参考,包括 San Francisco 字体系统、文本样式、动态类型、字间距、行距以及通过 iOS 26 实现的国际化。
SF Pro 和 SF Pro Rounded (iOS, iPadOS, macOS, tvOS)
SF Compact 和 SF Compact Rounded (watchOS, 窄列)
SF Mono (代码环境,等宽文本)
New York (衬线系统字体)
访问方式:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
// iOS/macOS
let descriptor = UIFontDescriptor(fontAttributes: [
.family: "SF Pro",
kCTFontWidthTrait: 1.0 // 1.0 = Expanded
])
SF Arabic (WWDC 2022)
可变字体根据点大小自动调整光学尺寸:
来自 WWDC 2020:
"TextKit 2 抽象化了字形处理,为国际化文本提供一致的体验。"
| 文本样式 | 默认大小 (iOS) | 使用场景 |
|---|---|---|
.largeTitle | 34pt | 主要页面标题 |
.title | 28pt | 次要标题 |
.title2 | 22pt | 三级标题 |
.title3 | 20pt | 四级标题 |
.headline | 17pt (半粗) | 强调的正文文本 |
.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())
按文本样式的实际字重:
紧密行距 (在 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)
// 同时缩放常量
let spacing = bodyMetrics.scaledValue(for: 20.0)
// SwiftUI - .font(.custom(_:relativeTo:))
Text("Custom scaled text")
.font(.custom("Avenir-Medium", size: 34, relativeTo: .body))
// @ScaledMetric 用于数值
@ScaledMetric(relativeTo: .body) var padding: CGFloat = 20
macOS
watchOS
visionOS
字间距调整字母之间的空间。对于光学尺寸行为至关重要。
特定尺寸的字间距表:
SF Pro 包含随点大小变化以保持最佳间距的字间距值:
来自 Apple 设计资源的示例:
紧密字间距 API (用于适应文本):
// UIKit
textView.allowsDefaultTightening(for: .byTruncatingTail)
// SwiftUI
Text("Long text that needs to fit")
.lineLimit(1)
.minimumScaleFactor(0.5) // 允许紧密字间距
手动字间距:
// UIKit
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.preferredFont(forTextStyle: .body),
.kern: 2.0 // 2pt 字间距
]
// SwiftUI
Text("Tracked text")
.tracking(2.0)
.kerning(2.0) // 替代 API
重要提示: 为了语义正确性,请使用 .tracking() 而非 .kerning() API。字间距在必要时会禁用连字;字距调整则不会。
默认行高: 根据字体内置指标(上升部 + 下降部 + 行间距)计算。
语言感知调整: iOS 17+ 自动为具有较高上升部/下降部的文字增加行高:
来自 WWDC 2023:
"为具有可变高度的文字自动调整行高"
手动行距:
// UIKit
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 8.0 // 8pt 额外空间
// SwiftUI (iOS 13+)
Text("Custom spacing")
.lineSpacing(8.0)
行高 (iOS 26+):
.lineHeight() 直接设置基线到基线的距离 — 比 .lineSpacing()(测量底部到顶部)更直观。
// 预设
Text("Open layout").lineHeight(.loose)
Text("Compact layout").lineHeight(.tight)
// 精确控制
Text("Scaled").lineHeight(.multiple(factor: 1.5))
Text("Fixed").lineHeight(.exact(points: 30)) // 不随动态类型缩放
也可用作 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 的 Text 中使用 AttributedString 时,如果字体来自环境而非属性化内容,段落样式(如 lineHeightMultiple)可能会丢失。
来自 WWDC 2025-280:
"TextEditor 会为任何值为 nil 的 AttributedStringKeys 替换为从环境计算出的默认值。"
同样的原则适用于 Text — 当你的 AttributedString 未指定字体时,SwiftUI 会应用环境字体,这可能导致它重建文本运行并丢弃或规范化段落样式细节。
// ❌ 错误 - .font() 修饰符可能覆盖并丢弃段落样式
var s = AttributedString(longString)
// 设置段落样式
var p = AttributedString.ParagraphStyle()
p.lineHeightMultiple = 0.92
s.paragraphStyle = p
// ⚠️ AttributedString 中未设置字体
Text(s)
.font(.body) // ⚠️ 可能重建运行,丢失 lineHeightMultiple
失败原因:
AttributedString 未设置字体属性(值为 nil).font(.body) 修饰符告诉它"对整个运行使用此字体"当你需要精细控制时,将排版保留在 AttributedString 内部:
// ✅ 正确 - 字体在 AttributedString 中,无环境覆盖
var s = AttributedString(longString)
// 在属性化内容内部设置字体
s.font = .system(.body) // ✅ 排版在 AttributedString 内部
// 设置段落样式
var p = AttributedString.ParagraphStyle()
p.lineHeightMultiple = 0.92
s.paragraphStyle = p
Text(s) // ✅ 无 .font() 修饰符
成功原因:
nil).font() 修饰符的环境覆盖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) // 无修饰符
使用时机:
Text 和 TextEditor 中显示Text("Simple text")
.font(.body)
.lineSpacing(4.0) // SwiftUI 级别的间距
使用时机:
var s = AttributedString("Title")
s.font = .system(.title).bold()
var body = AttributedString(" and body text")
body.font = .system(.body)
s.append(body)
Text(s) // ✅ 无 .font() 修饰符保留两种字体
// ❌ 错误思维模型:"先创建 AttributedString"
var s = AttributedString(text)
var p = AttributedString.ParagraphStyle()
p.lineHeightMultiple = 0.92
s.paragraphStyle = p
s.font = .system(.body) // ⚠️ 最后设置字体无济于事,如果你使用了 .font() 修饰符
Text(s).font(.body) // 仍然会破坏!
问题不在于你何时在 AttributedString 中设置字体。问题在于属性化内容是否携带自己的字体属性,还是依赖于 SwiftUI 的 .font(...) 环境。
当使用带有段落样式的 AttributedString 时:
AttributedString 内部设置(非 nil)Text 视图上没有 .font() 修饰符(除非有意覆盖)复杂文字示例(来自 WWDC 2021):
卡纳达语单词 "October":
这就是为什么 TextKit 2 使用 NSTextLocation 而不是整数索引。
希伯来语/阿拉伯语选择: 单个视觉选择 = AttributedString 中的多个 NSRange,由于从右到左布局。
语言感知 (iOS 17+):
均匀换行 (TextKit 2): 两端对齐段落使用改进的换行算法:
最佳实践:
.lineLimit(nil) 或 .lineLimit(2...5).minimumScaleFactor()系统 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; /* 已弃用,请使用 system-ui */
Text("Recipe Editor")
.font(.largeTitle.bold()) // 强调变体
let customFont = UIFont(name: "Avenir-Medium", size: 17)!
let metrics = UIFontMetrics(forTextStyle: .body)
label.font = metrics.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
let descriptor = UIFontDescriptor
.preferredFontDescriptor(withTextStyle: .largeTitle)
.withDesign(.rounded)!
let font = UIFont(descriptor: descriptor, size: 0)
Text("Today")
.font(.largeTitle.bold())
.fontDesign(.rounded)
struct RecipeView: View {
@ScaledMetric(relativeTo: .body) var padding: CGFloat = 20
var body: some View {
Text("Recipe")
.padding(padding) // 随动态类型缩放
}
}
WWDC : 2020-10175, 2022-110381, 2023-10058
文档 : /uikit/uifontdescriptor, /uikit/uifontmetrics, /swiftui/font
每周安装量
97
仓库
GitHub 星标数
606
首次出现
2026年1月21日
安全审计
安装于
opencode81
claude-code76
codex75
gemini-cli74
cursor73
github-copilot71
Complete reference for typography on Apple platforms including San Francisco font system, text styles, Dynamic Type, tracking, leading, and internationalization through iOS 26.
SF Pro and SF Pro Rounded (iOS, iPadOS, macOS, tvOS)
SF Compact and SF Compact Rounded (watchOS, narrow columns)
SF Mono (Code environments, monospaced text)
New York (Serif system font)
Access via:
// iOS/macOS
let descriptor = UIFontDescriptor(fontAttributes: [
.family: "SF Pro",
kCTFontWidthTrait: 1.0 // 1.0 = Expanded
])
SF Arabic (WWDC 2022)
Variable fonts automatically adjust optical size based on point size:
From WWDC 2020:
"TextKit 2 abstracts away glyph handling to provide a consistent experience for international text."
| Text Style | Default Size (iOS) | Use Case |
|---|---|---|
.largeTitle | 34pt | Primary page headings |
.title | 28pt | Secondary headings |
.title2 | 22pt | Tertiary headings |
.title3 | 20pt | Quaternary headings |
.headline | 17pt (Semibold) | Emphasized body text |
.caption2 for readable content — at 11pt, it's acceptable for timestamps and metadata annotations but too small for body text or labels users need to read. Prefer .caption or .footnote as the minimum for readable content.Apply .bold symbolic trait to get emphasized variants:
// 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())
Actual weights by text style:
Tight Leading (reduces line height by 2pt on iOS, 1pt on watchOS):
// UIKit
let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
let tightDescriptor = descriptor.withSymbolicTraits(.traitTightLeading)!
// SwiftUI
Text("Compact text")
.font(.body.leading(.tight))
Loose Leading (increases line height by 2pt on iOS, 1pt on watchOS):
// SwiftUI
Text("Spacious paragraph")
.font(.body.leading(.loose))
Automatic Scaling (iOS): Text styles scale automatically based on user preferences from Settings → Display & Brightness → Text Size.
Custom Fonts with Dynamic Type:
// 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
watchOS
visionOS
Tracking adjusts space between letters. Essential for optical size behavior.
Size-Specific Tracking Tables:
SF Pro includes tracking values that vary by point size to maintain optimal spacing:
Example from Apple Design Resources:
Tight Tracking API (for fitting text):
// UIKit
textView.allowsDefaultTightening(for: .byTruncatingTail)
// SwiftUI
Text("Long text that needs to fit")
.lineLimit(1)
.minimumScaleFactor(0.5) // Allows tight tracking
Manual 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
Important: Use .tracking() not .kerning() API for semantic correctness. Tracking disables ligatures when necessary; kerning does not.
Default Line Height: Calculated from font's built-in metrics (ascender + descender + line gap).
Language-Aware Adjustments: iOS 17+ automatically increases line height for scripts with tall ascenders/descenders:
From WWDC 2023:
"Automatic line height adjustment for scripts with variable heights"
Manual Leading:
// UIKit
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 8.0 // 8pt additional space
// SwiftUI (iOS 13+)
Text("Custom spacing")
.lineSpacing(8.0)
Line Height (iOS 26+):
.lineHeight() sets baseline-to-baseline distance directly — more intuitive than .lineSpacing() (which measures bottom-to-top).
// 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
Also available as AttributedString.lineHeight for styled strings. See axiom-swiftui-26-ref for full API details.
New in iOS 18: Font vendors can embed tracking tables in custom fonts using STAT table + CTFont optical size attribute.
let attributes: [String: Any] = [
kCTFontOpticalSizeAttribute as String: pointSize
]
let descriptor = CTFontDescriptorCreateWithAttributes(attributes as CFDictionary)
let font = CTFontCreateWithFontDescriptor(descriptor, pointSize, nil)
Critical Pattern When using AttributedString with SwiftUI's Text, paragraph styles (like lineHeightMultiple) can be lost if fonts come from the environment instead of the attributed content.
From WWDC 2025-280:
"TextEditor substitutes the default value calculated from the environment for any AttributedStringKeys with a value of nil."
This same principle applies to Text—when your AttributedString doesn't specify a font, SwiftUI applies the environment font, which can cause it to rebuild text runs and drop or normalize paragraph style details.
// ❌ 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
Why this fails:
AttributedString has no font attribute set (value is nil).font(.body) modifier tells it "use this font for the whole run"Keep typography inside the AttributedString when you need fine control:
// ✅ 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
Why this works:
nil).font() modifiervar 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
When to use:
Text and TextEditorText("Simple text")
.font(.body)
.lineSpacing(4.0) // SwiftUI-level spacing
When to use:
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!
The issue isn't when you set the font in AttributedString. The issue is whether the attributed content carries its own font attributes versus relying on SwiftUI's .font(...) environment.
When using AttributedString with paragraph styles:
AttributedString (not nil).font() modifier on Text view (unless intentionally overriding)Complex Script Example (from WWDC 2021):
Kannada word "October":
This is why TextKit 2 uses NSTextLocation instead of integer indices.
Hebrew/Arabic Selection: Single visual selection = multiple NSRanges in AttributedString due to right-to-left layout.
Language-Aware (iOS 17+):
Even Line Breaking (TextKit 2): Justified paragraphs use improved line breaking algorithm:
Best Practices:
.lineLimit(nil) or .lineLimit(2...5) in SwiftUI.minimumScaleFactor() for constrained single-line textSystem UI Font Families:
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 */
Legacy:
font-family: -apple-system; /* deprecated, use system-ui */
Text("Recipe Editor")
.font(.largeTitle.bold()) // Emphasized variant
let customFont = UIFont(name: "Avenir-Medium", size: 17)!
let metrics = UIFontMetrics(forTextStyle: .body)
label.font = metrics.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
let descriptor = UIFontDescriptor
.preferredFontDescriptor(withTextStyle: .largeTitle)
.withDesign(.rounded)!
let font = UIFont(descriptor: descriptor, size: 0)
Text("Today")
.font(.largeTitle.bold())
.fontDesign(.rounded)
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
Docs : /uikit/uifontdescriptor, /uikit/uifontmetrics, /swiftui/font
Weekly Installs
97
Repository
GitHub Stars
606
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode81
claude-code76
codex75
gemini-cli74
cursor73
github-copilot71
Flutter布局指南:构建响应式UI的约束规则与自适应设计模式
1,200 周安装
.body | 17pt | Primary body text |
.callout | 16pt | Secondary body text |
.subheadline | 15pt | Tertiary body text |
.footnote | 13pt | Footnotes, captions |
.caption | 12pt | Small annotations |
.caption2 | 11pt | Smallest annotations |