让你的 Mac 应用对每个人都更易用
Make your Mac app more accessible to everyone
2025年6月9日
一句话判断
如果你的 Mac 应用只做了基础的 accessibility 标签而没有优化容器结构和键盘导航,VoiceOver 用户可能需要按几十次键盘才能到达他们想去的地方——这场 Session 教你怎么用几个 modifier 把这个数字砍掉一半。
这场 Session 讲了什么
Mac 应用的无障碍和 iPhone/iPad 有一个关键区别:Mac 的界面更密集,嵌套容器层级更深。VoiceOver 在 Mac 上默认按容器导航,这意味着如果你的 accessibility 元素树结构不合理,用户每走一步都要反复”钻入”和”跳出”容器,体验极其低效。
这场 Session 从三个维度拆解了 Mac 无障碍优化:布局(Layout)、导航(Navigation)和交互(Interaction)。布局层面的核心是 accessibilityElement(children:) modifier,它提供 contain、combine、ignore 三种行为来控制子视图如何聚合成 accessibility 元素。导航层面引入了 rotors(转轮)和 accessibilityDefaultFocus modifier,前者让用户可以快速跳转到特定类型的元素集合,后者允许你为 VoiceOver 建议初始焦点位置。交互层面则强调了 hover-only 操作的替代方案——通过 accessibilityAction 为那些依赖鼠标悬停才能出现的控件提供等效的键盘操作路径。
值得深挖的点
容器层级的 trade-off
SwiftUI 在 Mac 上的 accessibility 元素天然形成树状结构,这和 iOS 的扁平化模型不同。accessibilityElement(children:) 的三个行为选项实际上是在做一种信息压缩决策:contain 创建子容器让导航更快但需要多一次”钻入”操作;combine 把多个子视图合并为一个元素减少导航步骤但牺牲了细粒度控制;ignore 完全屏蔽子视图适合装饰性内容。
实践中最常见的错误是过度嵌套。以 Session 中的格式检查器为例,原始状态下每个样式预设的标题和按钮是分开的 VoiceOver 焦点,用户要按 22 次键盘才能穿过整个检查器。把 VStack 设为 contain、HStack 设为 combine 之后,同样的操作只需要按几次。这背后的逻辑是:在 Mac 上,VoiceOver 默认走容器级导航,只有”钻入”后才看到子元素。所以 contain 在外层用来做粗粒度跳转,combine 在内层用来合并语义相关的元素——这个组合策略比全部用 combine 或全部用 contain 效果好得多。
accessibility sort priority 的语义
视觉布局和 accessibility 阅读顺序不一致的场景比想象中常见。比如一个列表项,视觉上作者名在书名左边,但 VoiceOver 用户更希望先听到书名以便快速筛选。accessibilitySortPriority modifier 允许你覆盖默认的阅读顺序,给特定视图更高的优先级。这和 CSS 的 order 属性有异曲同工之处,但注意它只影响 accessibility 技术的遍历顺序,不影响视觉布局。一个容易踩的坑是:当多个视图的 sort priority 相同时,系统按视觉位置(从上到下、从左到右)排序。
代码片段
用 contain 把样式预设分组,减少 VoiceOver 导航步骤:
VStack {
// 各个样式预设的 HStack
}
.accessibilityElement(children: .contain)
.accessibilityLabel("样式预设")
把标题和按钮合并为一个 accessibility 元素,一次按键即达:
HStack {
Text("Title")
Button("Apply") { applyStyle() }
}
.accessibilityElement(children: .combine)
通过 accessibility action 为 hover-only 的收藏按钮提供替代操作路径:
ForEach(pages) { page in
PageThumbnailView(page: page)
.accessibilityAction(.default) {
toggleBookmark(for: page)
}
}
最佳实践
建议在开发初期就用 VoiceOver 测试,而不是等功能完成后一次性修复。使用 accessibilityElement(children:) 时,优先在最外层用 contain 做大粒度分组,在内层用 combine 合并语义相关的元素对。
避免创建超过三层的嵌套容器。每多一层嵌套,VoiceOver 用户就多一次”钻入/跳出”的操作成本。对于装饰性视图,使用 accessibilityHidden(true) 或 ignore 行为直接屏蔽。
优先为所有通过 hover 或手势触发的操作提供 accessibility action 替代方案。这不仅是 VoiceOver 的需求,Switch Control 和 Voice Control 用户同样依赖这些 action。
还有什么值得关注
accessibilityRotor允许定义自定义转轮,让用户快速在特定集合(如收藏项、标题)之间跳转,类似网页的 landmark 导航。- macOS/iOS 26 新增
accessibilityDefaultFocusmodifier,允许你为 VoiceOver 建议初始焦点位置,而不是每次都从头开始导航。 - Accessibility Nutrition Labels 可以在 App Store 上展示你的应用支持哪些无障碍功能,这是一个给用户信心的信号。