关于 ios-accessibility
面向 SwiftUI、UIKit 和 AppKit 开发者的 iOS/macOS 无障碍技能,帮助设计可由 VoiceOver、Switch Control、Voice Control 和键盘操作的界面。内容涵盖焦点、动态字体、系统辅助功能设置及测试。
主要功能
- 01辅助技术语义与控件标签
- 02辅助功能焦点管理
- 03动态字体与系统偏好设置
- 04VoiceOver 转子和自定义操作
- 05UIKit 与 AppKit 无障碍模式
- 06无障碍测试与审核清单
适用场景
- 01审查界面是否可由 VoiceOver 操作
- 02确保弹窗关闭后焦点返回
- 03为手势操作提供辅助操作方式
- 04检查动态字体及减少动态效果适配
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill ios-accessibility安装后在 Claude Code 中输入/ios-accessibility确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/a11y-patterns.mdmedia-accessibility.mdnutrition-labels.md详细介绍
iOS/macOS 辅助功能 - SwiftUI、UIKit 和 AppKit
构建 SwiftUI、UIKit 和 AppKit 界面,确保用户可以通过 VoiceOver、Switch Control、Voice Control、Full Keyboard Access 和自适应辅助功能设置进行操作。
目录
- 核心原则
- VoiceOver 如何朗读元素
- SwiftUI 辅助功能修饰符
- 焦点管理
- 动态字体
- 自定转子
- 系统辅助功能偏好设置
- 装饰性内容
- Voice Control
- Switch Control
- Full Keyboard Access
- 辅助访问(iOS 18+)
- UIKit 辅助功能模式
- AppKit 辅助功能模式
- 辅助功能自定内容
- App Store 辅助功能营养标签
- 辅助功能测试
- 常见错误
- 审查清单
- 参考资料
核心原则
- 优先使用语义明确的系统控件;自定控件必须提供相同的名称、 角色、值、状态和操作。
- 确保通过辅助输入可以完成所有任务:不要让手势、颜色、 动画、悬停状态或视觉布局成为理解含义或执行操作的唯一途径。
- 在导航、呈现、更新和关闭过程中,有意识地管理焦点和遍历顺序。
- 让文本、布局、对比度、透明度和动画根据用户的 辅助功能设置进行适配。
- 将 App Store 辅助功能声明视为有依据的产品声明, 而不是对零散控件级支持的概述。
VoiceOver 如何朗读元素
VoiceOver 会按固定且不可配置的顺序朗读元素属性:
标签 -> 值 -> 特征 -> 提示
设计标签、值和提示时,请考虑这一朗读顺序。
SwiftUI 辅助功能修饰符
有关 SwiftUI 修饰符的详细示例(标签、提示、特征、分组、自定控件、可调整操作和自定操作),请参阅 references/a11y-patterns.md。
焦点管理
焦点管理是大多数应用容易出错的地方。关闭工作表、提醒或弹出窗口时,VoiceOver 焦点必须返回到触发该界面的元素。
本节介绍辅助技术的辅助功能焦点。有关键盘焦点、方向焦点、focusSection()、场景焦点值和 UIFocusGuide,请使用 focus-engine skill。
请在遍历顺序中检查元素顺序和分组;键盘焦点机制请参阅 focus-engine。
@AccessibilityFocusState(iOS 15+)
@AccessibilityFocusState 是一个属性包装器,用于读取和写入当前的辅助功能焦点。它可与 Bool 配合,用于单一目标焦点;也可与可选的 Hashable 枚举配合,用于多目标焦点。
struct ContentView: View {
@State private var showSheet = false
@AccessibilityFocusState private var focusOnTrigger: Bool
var body: some View {
Button("Open Settings") { showSheet = true }
.accessibilityFocused($focusOnTrigger)
.sheet(isPresented: $showSheet) {
SettingsSheet()
.onDisappear {
// Slight delay allows the transition to complete before moving focus
Task { @MainActor in
try? await Task.sleep(for: .milliseconds(100))
focusOnTrigger = true
}
}
}
}
}
使用枚举设置多目标焦点
enum A11yFocus: Hashable {
case nameField
case emailField
case submitButton
}
struct FormView: View {
@AccessibilityFocusState private var focus: A11yFocus?
var body: some View {
Form {
TextField("Name", text: $name)
.accessibilityFocused($focus, equals: .nameField)
TextField("Email", text: $email)
.accessibilityFocused($focus, equals: .emailField)
Button("Submit") { validate() }
.accessibilityFocused($focus, equals: .submitButton)
}
}
func validate() {
if name.isEmpty {
focus = .nameField // Move VoiceOver to the invalid field
}
}
}
自定模态界面
自定叠加视图需要 .isModal 特征来锁定 VoiceOver 焦点,并需要转义操作以关闭界面:
CustomDialog()
.accessibilityAddTraits(.isModal)
.accessibilityAction(.escape) { dismiss() }
将关闭行为纳入模态界面的约定进行测试:用户必须能够通过相应辅助技术的转义手势或键盘转义操作关闭叠加界面,并且焦点应返回到触发元素或下一个逻辑目标。
辅助功能通知(UIKit)
在 UIKit 场景中,需要播报更改或以命令方式移动焦点时:
// Announce a status change (e.g., "Item deleted", "Upload complete")
UIAccessibility.post(notification: .announcement, argument: "Upload complete")
// Partial screen update -- move focus to a specific element
UIAccessibility.post(notification: .layoutChanged, argument: targetView)
// Full screen transition -- move focus to the new screen
UIAccessibility.post(notification: .screenChanged, argument: newScreenView)
动态字体
使用系统文本样式缩放文本。非文本尺寸也应随之缩放:图标尺寸、间距、控件高度,以及需要跟随文本尺寸变化的自定义点击区域尺寸,都应使用 @ScaledMetric(relativeTo:)。
请参阅 references/a11y-patterns.md,了解动态字体和自适应布局示例,包括 @ScaledMetric 和最小点击目标模式。
自定义转子
转子可让 VoiceOver 用户快速导航到特定类型的内容。对于内容较多的屏幕,请添加自定义转子。完整的转子示例请参阅 references/a11y-patterns.md。
系统辅助功能偏好设置
始终遵循以下环境值:
@Environment(\.accessibilityReduceMotion) var reduceMotion
@Environment(\.accessibilityReduceTransparency) var reduceTransparency
@Environment(\.colorSchemeContrast) var contrast // .standard or .increased
@Environment(\.legibilityWeight) var legibilityWeight // .regular or .bold
减弱动态效果
将基于移动的动画替换为交叉淡化或无动画:
withAnimation(reduceMotion ? nil : .spring()) {
showContent.toggle()
}
content.transition(reduceMotion ? .opacity : .slide)
检查所有移动过渡,包括行删除、数量变化、工作表或结账界面的呈现,以及模态界面的关闭。在启用“减弱动态效果”时,将滑动、弹跳、视差、弹簧和大幅空间过渡替换为不透明度变化、即时状态变化或无动画。
降低透明度、提高对比度、粗体文本
// Solid backgrounds when transparency is reduced
.background(reduceTransparency ? Color(.systemBackground) : Color(.systemBackground).opacity(0.85))
// Stronger colors when contrast is increased
.foregroundStyle(contrast == .increased ? .primary : .secondary)
// Bold weight when system bold text is enabled
.fontWeight(legibilityWeight == .bold ? .bold : .regular)
装饰性内容
// Decorative images: hidden from VoiceOver
Image(decorative: "background-pattern")
Image("visual-divider").accessibilityHidden(true)
// Icon next to text: Label handles this automatically
Label("Settings", systemImage: "gear")
// Icon-only buttons: MUST have an accessibility label
Button(action: { }) {
Image(systemName: "gear")
}
.accessibilityLabel("Settings")
只有当图像所提供的信息不超出相邻辅助功能文本时,才将其视为装饰性内容。如果图像传达了产品款式、状态、图表数据点、用户生成内容或其他可区分的信息,请提供有意义的描述,而不是将其隐藏。
语音控制
语音控制依靠辅助功能标签生成可朗读的点击目标。如果标签缺失或无法朗读,语音控制就无法定位该元素。
- 每个交互元素都必须具有可朗读的辅助功能标签(不得仅使用表情符号或符号)。
- 同一可见屏幕中的标签必须唯一;重复标签会迫使用户借助覆盖层数字来区分。
- 对于冗长、拗口、本地化、包含大量缩写或常被缩短的语音标签,应将
accessibilityInputLabels视为冻结前的辅助功能工作;不要将其延后作为润色工作。语音控制和全键盘控制都会使用这些标签。按重要性降序列出替代说法。 - 对任何可见的、主要标签难以说出的目标广泛应用
accessibilityInputLabels,包括重复的行操作、数量控件、账户/设置链接、媒体控件,以及包含缩写或产品名称的本地化标签。 - 启用语音控制进行测试:说出“显示名称”和“显示数字”,确认所有交互元素都可定位。
- 进行语音控制审查时,验证两种覆盖层:“显示名称”可确认标签可朗读;“显示数字”可确认即使名称缺失、重复或拗口,仍可触达每个可见的交互目标。
请参阅 references/a11y-patterns.md,了解 accessibilityInputLabels 示例和可朗读标签指南。
切换控制
切换控制会按照阅读顺序依次扫描辅助功能元素。合理的分组和自定义操作对于易用性至关重要。
- 使用
.accessibilityElement(children: .combine)将相关内容分组,以减少扫描停靠点。 - 每个扫描目标都应有意义且可操作。对 VoiceOver 隐藏的装饰性元素,对切换控制也同样隐藏。
- 切换控制用户无法执行滑动删除、长按或多指手势。请改用
.accessibilityAction(named:)自定义操作来提供这些交互;切换控制会将其显示为菜单。 - 对于点击区域不标准的自定义控件,应确保
accessibilityFrame准确反映可点击区域(用于点扫描模式)。
请参阅 references/a11y-patterns.md,了解自定义操作和分组示例。
完整键盘访问
完整键盘访问(iOS/iPadOS 13.4+)让用户可以通过硬件键盘导航和操作应用。
审核每个控件是否都可访问、有标签、焦点状态清晰可见,并且无需触摸即可操作。将 Tab/方向键焦点路由、.focusable()、@FocusState、focusSection()、场景焦点值、tvOS 焦点和 UIFocusGuide 的实现交由 focus-engine 处理。
- 每个交互元素都可通过键盘访问并激活。
- 遍历顺序合理,且焦点不会被困住。
- 在所有对比度和文字大小设置下,焦点指示器都清晰可见。
- 纯手势操作都提供可通过键盘操作的替代方式。
- 应用快捷键不会覆盖 Cmd+C、Cmd+V 或 Cmd+Tab 等系统定义的快捷键。
完整键盘访问审核检查项请参阅 references/a11y-patterns.md。
遍历顺序
元素顺序和分组必须符合视觉顺序和任务顺序,适用于 VoiceOver 滑动顺序、切换控制扫描、语音控制叠加层和完整键盘访问审核。检查是否存在缺失或重复的标签、行内子元素过多、隐藏的自定义控件、焦点陷阱,以及顺序偏离任务流程的分组。键盘或方向键的路由机制交由 focus-engine 处理。
辅助访问(iOS 18+)
辅助访问为有认知障碍的用户提供简化界面。应用应支持此模式:
// Check if Assistive Access is active (iOS 18+)
@Environment(\.accessibilityAssistiveAccessEnabled) var isAssistiveAccessEnabled
var body: some View {
if isAssistiveAccessEnabled {
SimplifiedContentView()
} else {
FullContentView()
}
}
关键指南:
- 降低视觉复杂度:减少控件数量、增大点按目标、简化导航
- 使用清晰直白的标签和说明
- 减少一次呈现的选项数量
- 在“设置”>“辅助功能”>“辅助访问”中启用辅助访问并进行测试
UIKit 无障碍模式
对于自定义 UIKit 视图,应公开有意义的元素、标签、值、特征和操作;使用 insert/remove 修改特征;将自定义叠加层设为模态;并使用适当的播报、布局更改或屏幕更改通知。完整的 UIKit 示例请加载 references/a11y-patterns.md。
AppKit 无障碍模式
优先使用标准 AppKit 控件。对于自定义 NSView 或虚拟元素,应公开正确的 NSAccessibility 角色、标签、值、操作和状态更改通知。NSAccessibilityElement 和自定义控件示例请加载 references/a11y-patterns.md。
无障碍自定义内容
使用 .accessibilityCustomContent 提供有用的辅助信息,而不增加滑动停靠点;仅对应该自动朗读的内容使用高重要性。SwiftUI、UIKit 和 AppKit 示例请参阅 references/a11y-patterns.md。
App Store 无障碍营养标签
对于 App Store 无障碍营养标签、产品页面声明或 App Store Connect 无障碍问答,请阅读 references/nutrition-labels.md。
在建议作出声明前,必须有证据证明用户能够在相关设备类型上使用该功能完成所有常见任务。应使用结构化的“常见任务 × 无障碍功能”矩阵;如果音频内容需要字幕,还应纳入媒体文字稿;并明确提醒,App Store 无障碍问答必须准确,不得将其视为营销声明。
测试无障碍功能
手动测试
- Accessibility Inspector:针对模拟器和设备,检查标签、特征和对比度。
- VoiceOver 测试:在“设置”>“辅助功能”>“VoiceOver”中启用。通过滑动手势浏览每个屏幕。
- Voice Control 测试:在“设置”>“辅助功能”>“Voice Control”中启用。分别说出“Show Names”和“Show Numbers”;名称用于验证可朗读标签,数字则用于验证每个可见的交互目标都可触达,即使名称重复、缺失或不易理解也不例外。
- Full Keyboard Access 测试:在“设置”>“辅助功能”>“键盘”>“Full Keyboard Access”中启用。按 Tab 键浏览每个屏幕,确认所有交互元素都能获得焦点。
- Switch Control 测试:在“设置”>“辅助功能”>“Switch Control”中启用。确认扫描顺序合理,并且基于手势的交互会显示自定义操作。
- 动态字体:在“设置”>“辅助功能”>“显示与文字大小”>“更大字体”中测试所有文字大小。
使用 XCTest 进行自动化测试
使用稳定的辅助功能标识符定位 XCUIElement 值,然后断言元素是否存在、是否启用/选中、标签/值是否有意义,以及在测试环境可提供焦点信息时检查 hasFocus。覆盖关闭后的焦点恢复、每种模态的退出路径,以及手势的替代方式。UI 自动化是对 VoiceOver、Voice Control、Switch Control、键盘、动态字体、对比度、减少动态效果和减少透明度测试的补充,而非替代。加载 references/a11y-patterns.md 查看 XCTest 示例。
常见错误
| 错误 | 修复方法 |
|---|---|
| 特征赋值覆盖了原有行为 | 插入/移除 UIKit 特征,或使用 SwiftUI 辅助功能特征修饰符。 |
| 关闭后焦点丢失 | 将辅助功能焦点返回触发控件。 |
| 行导致滑动停靠点过多 | 有意地对相关子元素进行分组。 |
| 标签重复说明控件类型,或未说明图标含义 | 使用简洁、易于朗读的操作/名称;特征会播报类型。 |
| 动效、文字大小、对比度或透明度固定不变 | 根据对应的辅助功能偏好进行响应,并使用自适应文字样式。 |
| 目标过小或仅依赖颜色 | 提供 44×44 的目标,并辅以文字、形状或图标语义。 |
| 自定义覆盖层不是模态 | 提供模态语义、退出操作和焦点恢复。 |
审查清单
对于每项常见任务,请记录以下检查项的验证证据:
- 语义:控件提供简洁的名称、正确的角色/状态/值,并为装饰性、仅依赖颜色、仅依赖手势或可调整的内容提供替代方式。
- 导航:分组和遍历顺序均经过设计;模态提供模态和退出行为;焦点会返回发起操作的控件。
- 输入:Voice Control 名称易于朗读且各不相同,Show Names/Numbers 均可正常使用,Switch Control 提供手势替代方式,并且 Full Keyboard Access 不存在无法触达的控件、焦点陷阱或被覆盖的系统快捷键。
- 适配:在有代表性的布局和状态中,验证动态字体、减少动态效果、减少透明度、提高对比度、粗体文字和 44x44 点目标。
- 自动化:XCTest 覆盖稳定标识符、状态、可用时的焦点,以及每种模态的退出路径,但不能替代手动辅助技术测试。
- 并发:跨隔离边界传递的辅助功能值和通知负载符合
Sendable。 - 声明:每项 App Store 辅助功能声明都有已完成的常见任务证据矩阵作为依据,且涵盖声明支持的每种设备类型。
参考资料
- references/a11y-patterns.md — SwiftUI 和 UIKit 修饰符示例、分组、自定义操作、转子、动态字体
- references/nutrition-labels.md — App Store 辅助功能营养标签:当前类别及通过/不通过标准
- references/media-accessibility.md — 字幕、音频描述、AVMediaCharacteristic、SDH