Dynamic Type:关键概念、文本样式及其在 iOS 中的工作原理

作者: IT Sectr 发布日期: 2026-05-16 阅读时间: 9 分钟

Dynamic Type 是 iOS 的内置功能,可根据用户的系统设置自动调整应用程序中的字体大小。用户在「设置 → 显示与亮度 → 文字大小」中设定偏好的文本大小,所有支持 Dynamic Type 的应用程序无需额外操作即可自动适配。根据 Apple Human Interface Guidelines, 2024,支持 Dynamic Type 是在 App Store 发布的强制性要求,因为它是 iOS 平台无障碍的基本要素

要点

  • Dynamic Type — iOS 中根据系统字体大小设置自动缩放文本的机制
  • 通过文本样式(UIFont.TextStyle)提供支持 — body、headline、caption、title 等
  • 系统支持从 XSmall 到 AccessibilityXXXL 的 11 个缩放级别
  • 对于自定义字体,使用 UIFontMetrics 方法,可相对于 body 样式缩放任何字体
  • 布局必须是自适应的 — UIStackView、Auto Layout 和动态单元格高度是必需的

什么是 iOS 中的 Dynamic Type

Dynamic Type 是 iOS 7 引入的 iOS 技术,允许应用程序中的文本自动适应系统字体大小设置。用户可以同时增大或减小所有应用程序的字体 — 而无需在每个应用程序中单独更改设置。

根据 Apple WWDC 2023,「Make Your App Visually Accessible」,超过 40% 的 iOS 用户会更改默认字体大小。在 65 岁以上年龄组中,这一比例达到 70%。Dynamic Type 是 App Store 无障碍认证的强制性要求。缺乏支持是应用审核被拒的原因。

工作原理:应用程序使用 UIFont.preferredFont(forTextStyle:) 而非 UIFont(name:size:)。iOS 自动返回根据当前系统设置缩放的字体。当设置在设置中更改时,iOS 会生成 UIContentSizeCategory.didChangeNotification 通知 — 应用程序必须重新计算布局。

Dynamic Type 的发展历史

Dynamic Type 随 iOS 7 与扁平化设计一起出现。在 iOS 10 中,通过 UIFontMetrics 添加了对自定义字体的支持。iOS 11 引入了无障碍级别(最高 AccessibilityXXXL)。iOS 15 在 SwiftUI 中加入了 Dynamic Type 的自动支持。每次发布 Apple 都在加强要求:从 iOS 17 开始,不支持 Dynamic Type 的应用程序在上传到 App Store Connect 时会收到警告

UIFont.TextStyle 文本样式

iOS 提供 8 种文本样式,每种都有预定义的字体大小和字型用于标准设置:

样式常量大小 (L)用途
Large Title.largeTitle34px屏幕主标题
Title 1.title128px章节标题
Title 2.title222px副标题
Title 3.title320px卡片标题
Headline.headline17px (bold)加粗强调
Body.body17px正文
Callout.callout16px辅助文本
Caption 1.caption112px图片下方说明
Caption 2.caption211px小字说明
Footnote.footnote13px脚注、注释

使用正确的样式不仅仅是外观问题。UIFont.TextStyle.body 在 AccessibilityXXXL 模式下可达 53px。如果应用程序对 body 使用固定 17px 字体,对于放大了字体的用户来说,文本将变得不可读。

通过样式实现视觉层次

Dynamic Type 不仅缩放 — 它还保持视觉层次。Large Title 始终大于 Title 1,而 Title 1 又大于 Body,无论缩放级别如何。缩放系数各不相同:标题的缩放比正文更激进,以便即使在无障碍级别也能保持层次结构。

缩放级别

iOS 支持 11 个文本缩放级别,分为两类:

  • Standard sizes(5 级):XS、S、M、L(默认)、XL
  • Accessibility sizes(6 级):accessibilityXL、accessibilityXXL、accessibilityXXXL、accessibilityXXXXL、accessibilityXXXXXL、accessibilityXXXXXXL

body 样式的 XS 和 AccessibilityXXXL 之间的差异从 14px 到 53px — 将近 4 倍。针对 17px 设计的布局在 53px 时完全失效:文本超出边界,按钮重叠,单元格相互挤压。

在代码中检查当前类别:

swift
let category = UIApplication.shared.preferredContentSizeCategory
// .extraSmall, .small, .medium, .large, .extraLarge ...
if category.isAccessibilityCategory {
    // 启用替代布局
}

traitCollection 和文本大小

文本大小类别可通过 traitCollection.uiContentSizeCategory 获取。当系统设置更改时,iOS 会在所有 UIView 上调用 traitCollectionDidChange。在此方法中,您需要更新字体并重新计算布局。SwiftUI 自动完成此操作 — UIKit 需要手动订阅。

对于 UICollectionView,使用 UICollectionViewCompositionalLayout — 它会根据屏幕宽度和文本大小自动调整列数。在无障碍级别,从双列布局切换到单列布局,以免文本被截断和元素重叠。使用条件布局:regular width 时用两列,compact 或 accessibility 时用一列。

在 UIKit 中的实现

UIKit 中的基本实现 — UIFont.preferredFont(forTextStyle:)。此方法返回根据当前系统设置缩放的字体:

swift
titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
bodyLabel.font = UIFont.preferredFont(forTextStyle: .body)

对于自定义字体,使用 UIFontMetrics:

swift
let customFont = UIFont(name: "Montserrat-Regular", size: 16)!
titleLabel.font = UIFontMetrics(forTextStyle: .body)
    .scaledFont(for: customFont)

// 跟踪大小变化
NotificationCenter.default.addObserver(
    self,
    selector: #selector(preferredContentSizeChanged),
    name: UIContentSizeCategory.didChangeNotification,
    object: nil
)

对于 Interface Builder 中的 UILabel,只需将字体设置为文本样式(Text Style)并启用「Automatically Adjusts Font」。对于自定义字体,IB 不支持 UIFontMetrics — 只能通过代码实现。

UIFontMetrics 和缩放曲线

UIFontMetrics 使用与 preferredFont 相同的缩放曲线。您可以设置 fromTextStyle: .body、.headline 等。每种样式都有自己的缩放系数。Body 适度缩放,Large Title 激进缩放。如果您的自定义字体用于正文,请使用 .body。

SwiftUI 中的 Dynamic Type

在 SwiftUI 中,Dynamic Type 支持默认内置。所有系统修饰符(.font(.body)、.font(.title))都会自动缩放。开发人员无需调用 UIFont.preferredFont。

示例:

swift
Text("正文")
    .font(.body)
    .lineLimit(nil)
    .minimumScaleFactor(0.5)

对于自定义缩放,使用 DynamicTypeSize:

swift
@Environment(\.dynamicTypeSize) var dynamicTypeSize

var body: some View {
    Text("自适应文本")
        .font(.body)
        .padding(dynamicTypeSize <= .large ? 8 : 16)
}

SwiftUI 在系统文本大小更改时自动更新视图 — 无需额外订阅通知。

SwiftUI 中 Dynamic Type 的限制

.font(.body) 修饰符仅适用于系统字体。对于 SwiftUI 中的自定义字体,请在底层使用带有 UIFontMetrics 的 Font.custom。从 iOS 16 开始,SwiftUI 在 @Environment 中支持 DynamicTypeSize,允许调整间距和布局。

大字体布局适配

Auto Layout 是支持 Dynamic Type 的强制条件。固定宽度和高度(>=、<=)在无障碍尺寸下会失效。对 UILabel 使用 intrinsicContentSize — 它会根据当前字体大小自动计算高度。

自适应布局规则:

  • UIStackView 使用 distribution = fill 和 spacing = >= 8 — 元素将换行
  • UITableViewCell 使用 automaticDimension — 单元格高度自动适应内容
  • UILabel 上的 numberOfLines = 0 — 文本不会被截断,而是换行
  • 带有 edgeInsets 的按钮 — 通过 traitCollection.preferredContentSizeCategory 在无障碍级别添加边距

对于长文本(例如文章、使用条款),在 UILabel 上设置 minimumScaleFactor — 当 Auto Layout 无法容纳内容时,作为最后手段缩小文本。值为 0.5 表示字体最多可缩小到首选大小的 50%。

根据大小类别调整边距的示例:

swift
let isAccessibility = traitCollection
    .preferredContentSizeCategory.isAccessibilityCategory
stackView.spacing = isAccessibility ? 16 : 8
button.contentEdgeInsets = isAccessibility
    ? UIEdgeInsets(top: 16, left: 24, bottom: 16, right: 24)
    : UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)

无障碍级别布局的常见问题

文本截断 — 具有 numberOfLines = 1 和固定宽度的 UILabel 会在 AccessibilityXL 截断文本。解决方案:numberOfLines = 0 和 trailing constraint。元素重叠 — 固定高度的按钮相互重叠。解决方案:使用 automatic spacing 的 UIStackView。超出屏幕边界 — 对于无障碍级别,ScrollView 是必需的。

测试 Dynamic Type

Xcode 模拟器允许更改文本大小:在模拟器菜单中 Settings → Accessibility → Display & Text Size → Larger Text。在所有 11 个级别之间切换,检查文本是否被截断、按钮是否可用、布局是否失效。

对于自动化测试,在 XCUIApplication 中使用 contentSizeCategory 设置:

swift
func testAccessibilitySizes() {
    let app = XCUIApplication()
    app.launchArguments += [
        "-UIPreferredContentSizeCategoryName",
        "UICTContentSizeCategoryAccessibilityXXXL"
    ]
    app.launch()
    app.scrollViews.buttons["发送"].tap()
    XCTAssertTrue(app.staticTexts["表单已提交"]
        .waitForExistence(timeout: 5))
}

使用不同 launchArguments 的 XCUITest 可以在 CI 中测试所有缩放级别。不要忘记单独检查无障碍类别 — 它们最容易破坏布局。根据 Apple 的数据,70% 的 Dynamic Type 错误恰恰出现在无障碍级别,因为开发人员只测试标准尺寸。

Accessibility Inspector:检查 Dynamic Type

Xcode Accessibility Inspector 显示所选元素使用了哪些字体 — 固定字体还是可缩放字体。如果元素对模拟器中的文本大小更改无反应,则说明使用了固定字体。请替换为 UIFont.preferredFont 或 UIFontMetrics。

常见问题

用简单的话说,什么是 iOS 中的 Dynamic Type?

它是应用程序中根据系统大小设置自动缩放文本的功能。用户在「设置」中更改大小 — 所有支持的应用程序会自动调整,无需开发人员干预。

Dynamic Type 只适用于系统字体吗?

不是。对于自定义字体,请使用 UIFontMetrics(scaledFont:)。该方法会相对于指定的文本样式缩放任何字体,同时保持比例。

iOS 支持多少个缩放级别?

11 个级别:5 个标准级别(XS、S、M、L、XL)和 6 个无障碍级别(从 AccessibilityXL 到 AccessibilityXXXXXL)。body 字体大小范围从 14px 到 53px。

如何订阅文本大小更改?

在 UIKit 中通过 UIContentSizeCategory.didChangeNotification。在 SwiftUI 中使用 @Environment(\.dynamicTypeSize) — 视图会在系统设置更改时自动更新。

如果不支持 Dynamic Type 会怎样?

使用放大字体的用户将看到被截断的文本、重叠的元素和无法使用的按钮。应用程序可能因违反无障碍要求而在 App Store Review 中被拒绝。

总结

  • Dynamic Type — iOS 中根据系统设置缩放文本的内置机制,App Store 强制要求
  • 对系统字体使用 UIFont.preferredFont(forTextStyle:),对自定义字体使用 UIFontMetrics
  • iOS 支持 11 个缩放级别 — 从 XS 到 AccessibilityXXXXXL(body 为 14–53px)
  • 在 SwiftUI 中,Dynamic Type 通过 .font(.body) 和 @Environment(\.dynamicTypeSize) 自动工作
  • 布局必须是自适应的:UIStackView、表格中的 automaticDimension、numberOfLines = 0
  • 测试所有 11 个级别,尤其是无障碍类别 — 70% 的布局问题出现在这些级别
  • 对于 UIKit 应用程序,必须订阅 UIContentSizeCategory.didChangeNotification

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读