关于 swift-accessibility-skill
为 SwiftUI、UIKit 和 AppKit 提供无障碍实现与审查指导的技能,面向应用开发者。内容涵盖原生 API、代码模式及 App Store 无障碍营养标签的九个类别。
主要功能
- 01首稿即纳入无障碍要求
- 02SwiftUI、UIKit 和 AppKit 指导
- 03覆盖九类无障碍功能
- 04代码修复与审查流程
- 05测试和验证参考
适用场景
- 01为新界面添加无障碍支持
- 02改进现有视图的无障碍体验
- 03按无障碍类别审查应用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pasqualevittoriosi/ swift-accessibility-skill --skill swift-accessibility-skill安装后在 Claude Code 中输入/swift-accessibility-skill确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/before-after-appkit.mdbefore-after-swiftui.mdbefore-after-uikit.mdreferences/assistive-access.mddisplay-settings.mddynamic-type.mdmedia-accessibility.mdmotor-input.mdnutrition-labels.mdplatform-specifics.mdsemantic-structure.mdtesting-auditing.mdvoice-control.mdvoiceover-swiftui.mdvoiceover-uikit.mdwcag-mapping.mdresources/audit-template.swiftqa-checklist.md详细介绍
平台无障碍功能
概述
为所有受支持的平台实现 SwiftUI、UIKit 和 AppKit 无障碍功能。涵盖 App Store 无障碍营养标签的全部 9 个类别:VoiceOver、语音控制、更大文本、深色界面、无需颜色区分、足够的对比度、减少动态效果、字幕和音频描述。
此技能优先采用原生平台 API(可免费提供自动支持),并提供基于事实的指导,不涉及架构方面的主张。
首稿规则
在首稿中加入无障碍功能——绝不要先写一个没有无障碍支持的元素,再事后补上。事后补充无障碍功能更难、容易被跳过,效果也不如从一开始就实现。
除非某种模式不明显,否则不添加行内注释。对推断出的标签添加 // [VERIFY],因为 SF Symbol 名称并不总是符合预期的面向用户的含义。
| 情况 | 首次编写时的要求 |
|---|---|
Button / NavigationLink — 仅图标 | 添加 .accessibilityLabel("…"),并附上 // [VERIFY] |
Button / NavigationLink — 可见文本 | 无需额外处理——文本会自动作为标签 |
Image — 有意义 | 添加 .accessibilityLabel("…") |
Image — 装饰性 | 添加 .accessibilityHidden(true) |
withAnimation / .transition / .animation | 添加 @Environment(\.accessibilityReduceMotion),并设置动画条件 |
.font(.system(size:)) | 替换为 .font(.body) 或 @ScaledMetric |
| 颜色用于传达状态 | 同时添加形状、图标或文本 |
在非 Button 上使用 onTapGesture | 添加 .accessibilityElement(children: .ignore) + .accessibilityAddTraits(.isButton) + .accessibilityLabel |
| 自定义滑块 / 开关 / 步进器 | 添加 .accessibilityRepresentation { … },或 .accessibilityValue + .accessibilityAdjustableAction |
| 异步内容变化 | 发布公告并添加可用性检查(iOS 17+ 使用 AccessibilityNotification.Announcement,较早版本回退到 UIAccessibility.post) |
系统 .sheet / .fullScreenCover | 无需额外处理——SwiftUI 会自动限制焦点(自定义浮层仍需管理焦点) |
AVPlayer / 视频 | 使用 AVPlayerViewController——自动提供字幕和音频描述 |
| 自定义可点击视图 | 添加 .frame(minWidth: 44, minHeight: 44) |
| 任何新的 SwiftUI 视图 | 使用 Xcode Canvas Variants 验证(见无障碍功能摘要) |
NSButton — 仅图标(AppKit) | 调用 setAccessibilityLabel("…"),并附上 // [VERIFY] |
自定义交互式 NSView 元素(AppKit) | 调用 setAccessibilityElement(true) + 设置角色(setAccessibilityRole(.button))+ 设置标签 |
| AppKit 模态 / 弹出式 UI | 限制焦点,并确保可通过键盘和 VoiceOver 访问关闭操作 |
| 任何新的 AppKit 视图 / 控制器 | 使用 Accessibility Inspector 和完整键盘导航进行验证 |
优先使用原生控件(Button、Toggle、Stepper、Slider、Picker、TextField)——它们会自动获得完整的无障碍支持。自定义交互视图需要显式处理。
对于 AppKit,优先使用原生控件(NSButton、NSPopUpButton、NSSlider、NSSegmentedControl、NSTextField),再考虑自定义 NSView 交互。
示例——仅图标按钮:
Button {
shareAction()
} label: {
Image(systemName: "square.and.arrow.up")
}
.accessibilityLabel("Share") // [VERIFY] confirm label matches intent
示例——根据减少动态效果设置动画条件:
@Environment(\.accessibilityReduceMotion) private var reduceMotion
withAnimation(reduceMotion ? nil : .spring()) {
isExpanded.toggle()
}
完整的测试与验证流程 → references/testing-auditing.md
工作流
参考资料路由规则
回答前,选择一份与用户意图最匹配的主要参考文件,并优先加载。仅当请求明确涉及多个领域(例如 VoiceOver + 动态字体 + WCAG 映射),或主要文件未涵盖所需条件时,才加载其他参考文件。
1) 实现新代码
遵循首稿规则——在首稿中实现无障碍功能,不添加注释。
对于 iOS 15 之后引入的 API,始终添加 #available 检查,并为较早的操作系统版本提供回退行为。
编写完成后,对照首稿规则表进行验证——输出前修复所有遗漏。
代码之后附上无障碍功能摘要(见下文)。
2) 改进或修复现有代码
静默应用修复,不添加说明。
对于 iOS 15 之后引入的 API,始终添加 #available 检查,并提供旧版 OS 的回退行为。
修复后,对照 First-Draft Rules 表格进行验证——输出前修复所有差距。
代码之后,附上 无障碍摘要。
- 对于转换模式 →
examples/before-after-swiftui.md、examples/before-after-uikit.md或examples/before-after-appkit.md - 对于平台问题 →
references/platform-specifics.md
3) 审核现有代码
仅当用户明确提出要求(“audit”“how accessible is this?”、“review accessibility”)时进行。
快速修复模式 — 当用户要求仅处理阻断项/严重问题时(例如:“只修复阻断项”“快速修复”“仅处理严重问题”):只处理“阻断辅助技术”和“降低使用体验”问题。跳过“支持不完整”问题。
全面模式(默认)— 处理所有严重级别的问题,包括“支持不完整”和营养标签差距。
- 按类别识别问题 → 见下方的 分类处理指南
- 按下方的 审核输出格式组织内容
- 关于 WCAG 合规性映射 →
references/wcag-mapping.md - 移交给 QA →
resources/qa-checklist.md
4) 准备营养标签建议
→ references/nutrition-labels.md — 包含全部 9 个类别及官方通过/未通过标准
当用户要求准备或起草 App Store 无障碍营养标签建议时,按此格式输出:
**Accessibility Nutrition Label recommendation**
**App version evaluated:** [version or "Current build"]
**Scope reviewed:** [common tasks / screens evaluated]
**You could claim:**
- [labels where every common task is ✅ or —]
**Why you could claim them:**
- [label]: [brief reason tied to completed common-task coverage]
**You should not claim:**
- [labels blocked by any ❌]
- [labels that are not applicable]
**Why you should not claim them:**
- [label]: [blocked task or why the label is not applicable]
**Common-task verification**
| Common Task | VoiceOver | Voice Control | Larger Text | Dark Mode | No Color | Contrast | Motion | Captions | Audio Desc |
|---|---|---|---|---|---|---|---|---|---|
| [task] | ✅ / ❌ / — | ✅ / ❌ / — | ✅ / ❌ / — | ✅ / ❌ / — | ✅ / ❌ / — | ✅ / ❌ / — | ✅ / ❌ / — | ✅ / ❌ / — | ✅ / ❌ / — |
**Recommendation summary**
- You could claim: [labels]
- You should not claim: [labels]
不要不加限定地使用“claim”一词。请将输出表述为基于已审核范围的建议。
如果该列中的任何常见任务标记为 ❌,不要建议使用标签。只有当标签确实不适用于该应用或流程时,才使用 —。
无障碍摘要
所有代码生成和修复任务(模式 1、2)完成后附上,除非用户明确要求仅输出代码。不要添加前言。
**Accessibility applied:**
- [one bullet per pattern added — e.g. "`.accessibilityLabel` on icon-only Share button"]
**Verify in Xcode:**
- Use Canvas **Dynamic Type Variants** (grid icon → Dynamic Type Variants) to check layout at all text sizes
- Use Canvas **Color Scheme Variants** to check light and dark mode
- Use **Accessibility Inspector** (Xcode → Open Developer Tool) Settings tab to simulate Increase Contrast, Reduce Motion, Bold Text on the Simulator
**If Xcode is unavailable:**
- Run equivalent checks with platform accessibility inspector tools and manual setting toggles (Dynamic Type, Contrast, Reduce Motion, VoiceOver/Voice Control)
**Test on device:**
- [relevant items from Must Test on Device checklist]
如果没有添加任何内容(全部使用原生控件),则完全省略“已应用无障碍支持”。 除非用户询问,否则省略“营养标签就绪情况”。
审核输出格式
仅在用户明确要求审核时使用。代码生成或修复时绝不使用。
🔴 阻断辅助技术 — 完全无法访问,必须立即修复 🟡 降低使用体验 — 可以访问,但存在明显阻碍 🟠 支持不完整 — 存在导致无法获得营养标签认证的差距 ✅ 已在代码中验证 — 经静态分析确认正确
结尾处添加:
必须在设备上测试:列出 Review Checklist 中相关项目。 营养标签就绪情况:可实现 / 受 [问题] 阻碍 / 不适用。
核心指南
原则
- 首先识别框架和平台。 SwiftUI 和 UIKit 使用不同的 API;使用错误的 API 会导致静默失效。
- 每个修饰符都必须有语义上的理由。 给带有可见文本的
Button添加.accessibilityLabel实际上会造成负面影响——它会覆盖 VoiceOver 自动朗读的文本。 - 使用
#available检查 iOS 17+ API。 不进行可用性检查,特定版本的 API 会在旧版 OS 上崩溃。 - 对推断出的标签标记
[VERIFY]。 SF Symbol 名称(例如square.and.arrow.up)很少与用户期望听到的描述(“分享”)一致。推断出的标签需要人工审核。 - 不要根据
UIAccessibility.isVoiceOverRunning更改核心 UI 语义或布局。 应通过直接检查相关无障碍设置,根据用户的实际需求进行适配。有限的协调例外是允许的,例如避免语音重叠,或在辅助技术启用时延长临时内容的显示时间。 - 营养标签要求完整覆盖所有流程。 声称“支持 VoiceOver”意味着每个用户流程都能正常使用——包括登录、引导、购买和设置,而不只是主屏幕。
- 在浅色和深色模式下都测试对比度。 在浅色模式下达到 WCAG 4.5:1 的颜色组合,在深色模式下往往会因背景值不同而不达标。
VoiceOver
- 每个非装饰性元素都需要简洁、独立于上下文的标签
- 仅图标按钮需要
.accessibilityLabel——不能为空 - 装饰性图片:
.accessibilityHidden(true) - 状态应作为特征,而非标签:使用
.accessibilityAddTraits(.isSelected),而不是"Selected photo" - 将相关元素分组:
.accessibilityElement(children: .combine) - 播报动态变化:iOS 17+ 使用
AccessibilityNotification.Announcement("Upload complete").post(),回退方案为UIAccessibility.post(notification: .announcement, argument: "Upload complete") - 深入参考 →
references/voiceover-swiftui.md或references/voiceover-uikit.md
Voice Control
- 标签必须与可见文本完全一致——不一致会导致“Tap [name]”静默失效
- 对仅图标元素使用
.accessibilityInputLabels(["Compose", "New Message"]) - 每个交互元素都必须出现在“显示编号”和“显示名称”叠加层中
- 仅通过滑动才能显示的 UI 需要提供可通过语音访问的替代方式(
.accessibilityAction) - 深入参考 →
references/voice-control.md
Dynamic Type
- 仅使用文本样式:使用
.font(.body),而不是.font(.system(size: 16)) - 缩放自定义值:
@ScaledMetric(relativeTo: .body) var spacing: CGFloat = 8 - 固定尺寸的 UI 控件:
.accessibilityShowsLargeContentViewer() - 自适应布局:优先使用
ViewThatFits(iOS 16+),而不是手动检查dynamicTypeSize——它会自动选择适配的布局 - 深入参考 →
references/dynamic-type.md
Display Settings
- 减少动态效果:用溶解/淡出替代有意义的动画;移除装饰性动画
- 对比度:使用语义颜色(
Color(.label));文本的 WCAG 对比度为 4.5:1,非文本内容为 3:1 - 不仅依靠颜色区分:在颜色之外添加形状/图标/文本
- 降低透明度:启用时将
.ultraThinMaterial替换为不透明效果 - 深入参考 →
references/display-settings.md
Semantic Structure
- 阅读顺序:使用
.accessibilitySortPriority(_:)(值越高,越先朗读) - 新屏幕获得焦点:发送
.screenChanged通知 - 模态焦点:
accessibilityViewIsModal = true - 自定义导航:
accessibilityRotor(_:entries:) - 深入参考 →
references/semantic-structure.md
Motor / Input
- 触控目标:最小尺寸为 44×44pt
- 键盘:每个元素都可通过 Tab 访问,每个模态都可通过 Escape 关闭
- Switch Control:为仅支持滑动的手势使用
UIAccessibilityCustomAction - 深入参考 →
references/motor-input.md
快速参考
SwiftUI 修饰符
| 修饰符 | 用途 |
|---|---|
.accessibilityLabel(_:) | 为非文本元素提供 VoiceOver 文本 |
.accessibilityHint(_:) | 简要描述操作结果 |
.accessibilityValue(_:) | 当前值(滑块、进度) |
.accessibilityHidden(true) | 隐藏装饰性元素 |
.accessibilityAddTraits(_:) | 语义角色或状态 |
.accessibilityRemoveTraits(_:) | 移除继承的特征 |
.accessibilityElement(children:) | .combine / .contain / .ignore |
.accessibilitySortPriority(_:) | 阅读顺序(值越高,越靠前) |
.accessibilityAction(_:_:) | 命名的自定义操作 |
.accessibilityAdjustableAction(_:) | 增加/减少 |
.accessibilityInputLabels(_:) | Voice Control 的备用名称 |
.accessibilityFocused(_:) | 以编程方式设置焦点 |
.accessibilityRotor(_:entries:) | 自定义 VoiceOver 转子 |
.accessibilityRepresentation(_:) | 替换自定义控件的 AX 树 |
.accessibilityIgnoresInvertColors(true) | 在智能反色中保护图片 |
.accessibilityShowsLargeContentViewer() | 为固定尺寸 UI 显示大内容查看器 |
@Environment 值
| 值 | 用途 |
|---|---|
\.accessibilityReduceMotion | 控制动画是否启用 |
\.accessibilityReduceTransparency | 替换模糊效果 |
\.accessibilityDifferentiateWithoutColor | 添加非颜色指示标记 |
\.colorSchemeContrast | .standard / .increased |
\.dynamicTypeSize | 当前文本大小 |
营养标签 → API
| 标签 | 主要 API | 参考资料 |
|---|---|---|
| VoiceOver | accessibilityLabel、特征、操作、转子 | voiceover-swiftui.md、voiceover-uikit.md |
| Voice Control | accessibilityInputLabels、匹配可见文本 | voice-control.md |
| 更大文本 | @ScaledMetric、文本样式、大内容查看器 | dynamic-type.md |
| 深色界面 | colorScheme、语义颜色 | display-settings.md |
| 不仅通过颜色区分 | 形状 + 颜色 | display-settings.md |
| 足够的对比度 | WCAG 文本 4.5:1 / 非文本 3:1 | display-settings.md |
| 减少动态效果 | accessibilityReduceMotion、动画开关 | display-settings.md |
| 字幕 | AVPlayerViewController | media-accessibility.md |
| 音频描述 | AVMediaCharacteristic.describesVideoForAccessibility | media-accessibility.md |
审核清单
可在代码中验证
- 仅图标按钮具有
.accessibilityLabel - 装饰性图像具有
.accessibilityHidden(true) - 状态通过特征表达,而非标签
- 不使用硬编码的字体大小 — 使用文本样式或
@ScaledMetric - 动画受
accessibilityReduceMotion控制 - 使用语义颜色(而非硬编码的十六进制值)
- 仅图标元素具有
.accessibilityInputLabels - 触控目标 ≥ 44×44pt
- 模态使用
.sheet()或accessibilityViewIsModal - 仅支持滑动的操作提供
.accessibilityAction替代方式 - 视频使用
AVPlayerViewController - 照片/地图/视频使用
.accessibilityIgnoresInvertColors() - XCUITest 包含带有
#available守卫(iOS 17+ / macOS 14+)的performAccessibilityAudit(),并为较旧的 OS 版本提供回退断言
必须在设备上测试
- VoiceOver:导航顺序、朗读流程、推送/模态后的焦点
- Voice Control:“Show numbers”覆盖范围、“Tap [name]”激活
- Switch Control:扫描路径、可访问的自定义操作
- 完整键盘访问:Tab 顺序、Escape 关闭
- 动态字体:最大尺寸下的布局 — 不得裁切或重叠
- 减少动态效果:验证所有动画
- 灰度滤镜:不依赖颜色也能理解信息
- 深色模式 + 增强对比度:验证两种模式下的对比度
- 字幕和音频描述:自动启用
完整测试流程 → references/testing-auditing.md
问题分级指南
阻碍辅助技术使用 — 立即修复
- 仅图标按钮没有标签 →
references/voiceover-swiftui.md - 图像缺少替代文本 →
references/voiceover-swiftui.md - 自定义视图未出现在辅助功能树中 →
references/voiceover-uikit.md - VoiceOver 循环朗读或无法移出元素 →
references/semantic-structure.md
降低使用体验 — 造成明显阻碍
- Voice Control 无法识别某个元素 →
references/voice-control.md - 语音标签与可见文本不匹配 →
references/voice-control.md - 触控目标 < 44×44pt →
references/motor-input.md - 仅通过颜色进行区分 →
references/display-settings.md - 朗读顺序错误 →
references/semantic-structure.md - 模态未锁定焦点 →
references/semantic-structure.md
支持不完整 — 无法声称符合营养标签要求
- 文本无法随动态字体缩放 →
references/dynamic-type.md - 动画忽略“减少动态效果”设置 →
references/display-settings.md - 深色模式下对比度过低 →
references/display-settings.md - 没有字幕或音频描述 →
references/media-accessibility.md - 平台不具备辅助功能 →
references/platform-specifics.md - 营养标签准备工作 →
references/nutrition-labels.md
故障排除
使用了错误框架的 API
症状: 在 UIKit/AppKit 代码中使用 SwiftUI 修饰符,或混用了不同框架的平台 API。
修复: 使用 API 前,先根据导入语句(import SwiftUI、import UIKit、import AppKit)确定框架。SwiftUI 使用修饰符,UIKit 使用 UIAccessibility 属性,AppKit 使用 NSAccessibility API。
过度标记原生控件
症状: 为已有可见文本的 Button("Save") 或 Toggle("Dark Mode") 添加了 .accessibilityLabel。
修复: 控件已有可见文本时,不要添加 .accessibilityLabel——它会覆盖自动生成的标签,导致标签与屏幕上显示的内容不一致。仅为纯图标或无文本元素添加标签。
API 要求的系统版本高于项目目标版本
症状: 代码使用了按平台版本提供的 API(iOS/macOS/tvOS/watchOS/visionOS),却没有进行可用性检查。
修复: 针对支持的每个目标操作系统,使用 #available 进行判断,并在需要时使用较旧的等效 API。常见替代方案:
AccessibilityNotification.Announcement("…").post()→UIAccessibility.post(notification: .announcement, argument: "…")performAccessibilityAudit()→ 手动编写 XCTest 断言ViewThatFits(iOS 16+)→ 使用@Environment(\.dynamicTypeSize)手动切换布局- 共享代码时,优先使用多平台条件判断:
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, visionOS 1, *) { ... }
缺少无障碍摘要
症状: 生成的代码中没有“已应用无障碍功能”/“在设备上测试”摘要块。 修复: 代码生成及修复任务完成后,始终附上无障碍摘要(工作流模式 1 和 2)。仅在未添加任何无障碍模式时省略(所有控件都是带可见文本的原生控件)。
根据推断得出的标签缺少 [VERIFY] 注释
症状: 根据 SF Symbol 名称或方法名称推断出的 .accessibilityLabel,没有附上 // [VERIFY] 注释。
修复: 任何推断得出的标签(而非用户提供的标签)都必须附上 // [VERIFY] confirm label matches intent 注释。像 square.and.arrow.up 这样的 SF Symbol 名称很少符合用户预期听到的内容。
参考资料
references/voiceover-swiftui.md— SwiftUI 无障碍修饰符、特征、操作、转子、播报references/voiceover-uikit.md— UIAccessibility 协议、自定义元素、容器、通知references/voice-control.md— 输入标签、“显示数字/名称”、支持语音操作的替代方案references/motor-input.md— 切换控制、全键盘访问、辅助触控、tvOS 焦点references/dynamic-type.md— Dynamic Type、@ScaledMetric、大内容查看器、自适应布局references/display-settings.md— 减少动态效果、对比度、深色模式、颜色、透明度、反转references/semantic-structure.md— 分组、阅读顺序、焦点管理、转子、模态焦点references/media-accessibility.md— 字幕、音频描述、语音合成、图表references/testing-auditing.md— 无障碍检查器、Xcode Canvas 变体、XCTest、performAccessibilityAudit()、手动测试references/nutrition-labels.md— 全部 9 项营养标签及通过/失败标准references/wcag-mapping.md— WCAG 2.2 A/AA 级成功标准与 SwiftUI/UIKit/AppKit API 的对应关系references/assistive-access.md— 辅助访问(iOS 17+)、设计原则、测试references/platform-specifics.md— macOS、watchOS、tvOS、visionOS 的特定说明examples/before-after-swiftui.md— SwiftUI 转换前后示例examples/before-after-uikit.md— UIKit 转换前后示例examples/before-after-appkit.md— AppKit(macOS)转换前后示例resources/audit-template.swift— 可直接使用的 XCUITest 文件,用于自动化无障碍审计(iOS 17+)resources/qa-checklist.md— 独立的 QA 手动测试清单(交给测试人员)