要点
Dynamic Type — Apple 技术,允许应用程序的字体在系统文本大小更改时自动缩放。用户在 Settings → Accessibility → Display & Text Size 中设置首选大小,所有支持 Dynamic Type 的应用程序无需重新启动即可调整字号。
系统提供 11 级缩放:从 XS(14pt 基础)到 XXXL(23pt 基础),以及 5 个额外的辅助功能尺寸(AX1–AX5)供有严重视力障碍的用户使用。当大小改变时,iOS 会生成 UIContentSizeCategory.didChangeNotification 通知,应用程序必须通过 UIFontMetrics 重新加载字体。根据 Apple Human Interface Guidelines,Dynamic Type 不会改变行间距和字母比例 — 只改变字号,从而在所有大小下保持可读性。
Dynamic Type 对于追求辅助功能状态(accessibility)的应用程序是必需的。在 WWDC 2024 上,Apple 宣布不支持动态类型的应用程序在审核时面临被拒的风险,如果其主要功能与文本阅读相关。在 IT Sectr 项目中,我们从第一个版本开始就实施 Dynamic Type — 这为后续的辅助功能适配节省了高达 20% 的时间。
Apple 提供11 种预定义的文本样式,每种样式都显示在 UIFontDescriptor.UIAttributes 中,用于系统字体 SF Pro。开发者不指定像素大小 — 他选择语义样式,iOS 根据用户设置应用相应的字号。
| 样式 | 大小(默认) | 大小(AX5) | 用途 |
|---|---|---|---|
| .largeTitle | 34pt | 62pt | 一级屏幕标题 |
| .title1 | 28pt | 50pt | 二级标题 |
| .title2 | 22pt | 40pt | 章节标题 |
| .title3 | 20pt | 36pt | 副标题 |
| .headline | 17pt semibold | 31pt | 列表中突出显示的文本 |
| .body | 17pt | 31pt | 正文 |
| .callout | 16pt | 29pt | 提示和附加文本 |
| .subheadline | 15pt | 27pt | 元素描述 |
| .footnote | 13pt | 23pt | 脚注和小文本 |
| .caption1 | 12pt | 21pt | 图片描述 |
| .caption2 | 11pt | 19pt | 标签和小描述 |
每种样式都使用具有固定字形的系统字体 SF Pro(正文用 Regular,标题用 Bold)。自定义字体通过 UIFontMetrics 连接 — 这是将第三方字体适配到动态类型的唯一正确方法。
UIFontMetrics — 在 iOS 11 中引入的类,它按比例缩放任意 UIFont 到所选的文本样式。在 iOS 11 之前,开发者直接调用 preferredFontForTextStyle,它只返回系统字体 SF Pro。UIFontMetrics 解决了这个问题:它接受任何字体并返回其缩放版本,同时保持字形和比例。
scaledFont(for:) 方法根据用户当前的文本大小计算缩放系数:如果选择了“大”(Large)大小,系数为 1.0;如果选择了“非常大”(AX1) — 约为 1.3;对于“最大”(AX5) — 最高 1.8。系数应用于自定义字体的基础大小,而不是系统字体 — 这确保了设计意图在所有大小下都得以保留。
建议对应用程序中的所有自定义字体使用 UIFontMetrics。在 WWDC 2024 上,Apple 展示了 Accessibility Inspector 工具,它可以检查 Dynamic Type 支持并突出显示使用固定大小而非 UIFontMetrics 的元素。在 IT Sectr,我们从 2018 年开始在所有项目中实施了 UIFontMetrics — 这将文本重叠的错误数量减少了 35%。
获取标签动态字体的最简单方法。代码展示了如何设置一个使用 .body 样式的 UILabel,它会自动响应系统文本大小的变化。
import UIKit
class GreetingViewController: UIViewController {
private let label: UILabel = {
let l = UILabel()
l.text = "Hello, Dynamic Type!"
l.font = UIFont.preferredFont(forTextStyle: .body)
l.adjustsFontForContentSizeCategory = true
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
adjustsFontForContentSizeCategory = true 标志在系统文本大小更改时自动更新字体 — 没有此标志,Dynamic Type 将无法用于现有标签。当大小更改时,iOS 调用 layoutIfNeeded,标签无需手动干预即可更新字体。
使用自定义字体(例如 Montserrat)时,preferredFontForTextStyle 调用将返回 SF Pro,而不是您的字体。UIFontMetrics 正是缩放自定义字体,保持其设计。
import UIKit
extension UIFont {
static func customDynamicFont(name: String,
size: CGFloat,
style: UIFont.TextStyle) -> UIFont {
guard let baseFont = UIFont(name: name, size: size) else {
return .preferredFont(forTextStyle: style)
}
return UIFontMetrics(forTextStyle: style).scaledFont(for: baseFont)
}
}
let titleFont = UIFont.customDynamicFont(
name: "Montserrat-Bold",
size: 20,
style: .title3
)
scaledFont(for:) 方法接受基础 UIFont 并返回与 .title3 样式成比例的缩放版本。在 AX5 辅助功能大小下,Montserrat-Bold 20pt 字体变为约 36pt — 正好是该样式的系统字体增长的量。在 IT Sectr,我们从 2018 年开始在所有具有自定义字体的项目中使用此模式,这完全消除了视力受限用户对小文本的投诉。
如果您手动更新字体(不使用 adjustsFontForContentSizeCategory),请订阅 UIContentSizeCategory.didChangeNotification 通知。这对于不继承自动字体更新的自定义 UI 元素是必需的。
import UIKit
class DynamicLabel: UILabel {
private var customFont: UIFont?
private var textStyle: UIFont.TextStyle = .body
func configure(font: UIFont, style: UIFont.TextStyle) {
customFont = font
textStyle = style
NotificationCenter.default.addObserver(
self,
selector: #selector(updateFont),
name: UIContentSizeCategory.didChangeNotification,
object: nil
)
updateFont()
}
@objc private func updateFont() {
guard let base = customFont else { return }
self.font = UIFontMetrics(forTextStyle: textStyle).scaledFont(for: base)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
订阅 didChangeNotification 保证自定义元素在系统设置发生任何更改时更新字体,无需重新启动应用程序。从 iOS 15 开始,即使在支持 Live Text Size 的应用程序中通过 Control Center 更改文本大小时,通知也会到达。
常见问题
Dynamic Type 在 iOS 7 及更高版本上可用。UIFontMetrics 出现在 iOS 11 中 — 在此之前直接使用 preferredFontForTextStyle 方法。在 iOS 15+ 上,动态类型支持额外的辅助功能大小(AX1–AX5)用于大字体的用户。支持 iOS 10 及更早版本的应用程序使用没有 UIFontMetrics 的 preferredFontForTextStyle,这将对字体的支持限制为仅系统字体 SF Pro。
在 Xcode 15+ 中,打开模拟器并选择 Settings → Accessibility → Display & Text Size → Larger Text。拖动滑块更改大小。在 Xcode 本身中,有 Debug → Accessibility Inspector,它可以突出显示具有固定字体的元素。对于自动化,使用通过 XCUITest 更改设置的 UISlider 的测试 — 这保证了所有屏幕都能正确重建。
是的,使用动态类型的 Auto Layout 需要在 UILabel 和 UIButton 中使用 intrinsicContentSize。在辅助功能大小下,文本可能超出 superview 的边界 — 添加 UIScrollView 或增加 leading/trailing 约束。对间距(padding)使用 UIFontMetrics.scaledValue,以使字母间距也能按比例随字体缩放。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。