WWDC Quick Look 💓 By SwiftGGTeam
Create accessible spatial experiences

Create accessible spatial experiences

观看原视频

Highlight

visionOS 为空间计算引入了完整的无障碍体系:VoiceOver 通过捏合手势组合在三维空间中导航,RealityKit 的 AccessibilityComponent 让 3D 实体可被屏幕阅读器识别,Dwell Control 让用户仅通过注视就能触发交互,而 head anchor 替代方案和 Reduce Motion 适配确保了运动敏感用户也能舒适使用。

核心内容

问题:3D 空间里的无障碍是空白地带

在传统屏幕上,VoiceOver 沿着一维顺序遍历 UI 元素——从上到下、从左到右。开发者给按钮加个 accessibilityLabel,给图片加个 accessibilityDescription,基本就完事了。

但空间计算把这一切推翻了。在 visionOS 上,UI 元素分布在三维空间中。一个按钮可能在你前方 2 米处,另一个在你左边 45 度、3 米外。VoiceOver 不能简单地”从上到下”遍历——它需要理解空间关系。

更麻烦的是,3D 场景里的对象不是 UIButton 或 UILabel,它们是 RealityKit 实体——由 3D 模型、材质、动画组成。这些实体对 VoiceOver 来说完全不可见。开发者如果不做额外处理,一个 3D 场景里的云朵、树木、角色,对视障用户来说就是一片虚无。

交互方式也变了。visionOS 没有触摸屏,交互靠眼睛注视和手指捏合。这对运动障碍用户来说是个全新的挑战:有的人无法做捏合手势,有的人无法稳定注视某个点。

Apple 的方案:把无障碍体系搬到三维空间

visionOS 在系统层面重新设计了整个无障碍体系。VoiceOver 不再是屏幕坐标遍历,而是空间感知导航——它用空间音频提示对象方位,让用户能”听”出对象在空间中的位置。

对于 3D 内容,Apple 引入了 AccessibilityComponent——这是 RealityKit 的一个组件,让任何实体都能被无障碍系统识别。你可以在实体上设置标签、值、特征,甚至添加自定义操作。这和 UIKit/SwiftUI 的无障碍 API 设计思路一致,但适配了三维场景。

运动无障碍方面,visionOS 提供了 Dwell Control(注视驻留控制)和 Switch Control(外部开关控制)。前者让用户只需注视目标并保持一段时间就能触发操作,不需要捏合手指。后者支持通过蓝牙开关设备来控制整个系统。

详细内容

让 RealityKit 实体可被 VoiceOver 识别

在 visionOS 上,一个 RealityKit 实体默认对无障碍系统不可见。你需要显式创建 AccessibilityComponent 并附加到实体上(05:28):

var accessibilityComponent = AccessibilityComponent()
accessibilityComponent.isAccessibilityElement = true
accessibilityComponent.traits = [.button, .playsSound]
accessibilityComponent.label = "Cloud"
accessibilityComponent.value = "Grumpy"
cloud.components[AccessibilityComponent.self] = accessibilityComponent

// ...

var isHappy: Bool {
    didSet {
        cloudEntities[id].accessibilityValue = isHappy ? "Happy" : "Grumpy"
    }
}

关键点:

  • AccessibilityComponent() 创建一个无障碍组件实例,用于描述 RealityKit 实体的无障碍属性
  • isAccessibilityElement = true 让 VoiceOver 能够发现这个实体
  • traits 设置为 .button.playsSound,告诉 VoiceOver 这个实体是一个按钮且播放声音,用户听到”按钮”提示后会知道可以激活它
  • label 是 VoiceOver 朗读的名称,value 是实体的当前状态
  • cloud.components[AccessibilityComponent.self] = accessibilityComponent 将组件附加到 RealityKit 实体上
  • 状态变化时,通过 didSet 更新 accessibilityValue,VoiceOver 会自动播报状态变更

为 RealityKit 实体添加激活操作

仅让 VoiceOver 能朗读实体还不够,用户还需要能与之交互。systemActions 让 VoiceOver 用户可以通过手势激活 3D 对象(08:04):

var accessibilityComponent = AccessibilityComponent()
accessibilityComponent.isAccessibilityElement = true
accessibilityComponent.traits = [.button, .playsSound]
accessibilityComponent.label = "Cloud"
accessibilityComponent.value = "Grumpy"
accessibilityComponent.systemActions = [.activate]
cloud.components[AccessibilityComponent.self] = accessibilityComponent

// ...

content.subscribe(to: AccessibilityEvents.Activate.self, componentType: nil) { activation in
    handleCloudCollision(for: activation.entity, gameModel: gameModel)
}

关键点:

  • systemActions = [.activate] 注册了激活操作,VoiceOver 用户可以用捏合手势触发
  • content.subscribe(to: AccessibilityEvents.Activate.self, ...) 监听无障碍激活事件
  • 回调闭包接收 activation 参数,从中可以获取被激活的实体 activation.entity
  • handleCloudCollision 是开发者自定义的处理函数,比如让云朵发出声音或改变表情
  • componentType: nil 表示监听所有实体类型的激活事件,不做过滤

播报上下文中的重要事件

空间应用中,场景里的变化可能发生在用户视野之外。比如一个对象从背后出现,或者远处发生了需要关注的事件。这时需要主动通知 VoiceOver 用户(09:23):

AccessibilityNotification.Announcement("8 clouds in front of you").post()

关键点:

  • AccessibilityNotification.Announcement 创建一个无障碍公告通知
  • .post() 将通知发送给系统,VoiceOver 会朗读括号中的文本
  • 这个 API 适用于场景中发生了用户可能看不到的重要变化——比如游戏得分、新敌人出现、计时器倒数
  • 公告应简洁、信息密集,直接描述”什么”和”在哪里”

为头部锚定内容提供替代方案

在空间应用中,UI 元素可以锚定在用户头部(head-anchored)——即始终跟随用户视线移动。但这对某些用户会造成不适,比如容易晕动或有前庭障碍的人。visionOS 提供了检测用户偏好的 API(13:15):

// SwiftUI
@Environment(\.accessibilityPrefersHeadAnchorAlternative)
private var accessibilityPrefersHeadAnchorAlternative

// UIKit
AXPrefersHeadAnchorAlternative()
NSNotification.Name.AXPrefersHeadAnchorAlternativeDidChange

关键点:

  • SwiftUI 中通过 @Environment 读取 accessibilityPrefersHeadAnchorAlternative 环境值,布尔值表示用户是否需要头部锚定的替代方案
  • UIKit 中调用 AXPrefersHeadAnchorAlternative() 函数获取当前偏好
  • AXPrefersHeadAnchorAlternativeDidChange 通知让 UIKit 开发者能监听偏好变化
  • 当该值为 true 时,开发者应将头部锚定的 UI 改为世界锚定(固定在空间中某个位置)或放置在窗口内
  • 这个偏好由系统在辅助功能设置中统一管理,不需要 App 自己提供开关

适配 Reduce Motion 设置

visionOS 同样支持 Reduce Motion(减少动态效果)设置。对于包含大量动画、粒子效果或空间运动的 App,检查这个设置可以避免让敏感用户不适(15:04):

// SwiftUI
@Environment(\.accessibilityReduceMotion)
private var accessibilityReduceMotion

// UIKit
UIAccessibility.isReduceMotionEnabled
UIAccessibility.reduceMotionStatusDidChangeNotification

关键点:

  • accessibilityReduceMotion 环境值反映系统的 Reduce Motion 设置
  • 当用户开启该设置后,开发者应减少或关闭以下内容:大幅度的对象动画、粒子系统效果、视差滚动、相机剧烈移动
  • isReduceMotionEnabled 是 UIKit 中的只读属性,在 App 启动时和设置变化时检查
  • reduceMotionStatusDidChangeNotification 通知让 App 实时响应设置变化
  • 一个常见的适配:把弹性动画改为简单的淡入淡出,把 3D 旋转效果去掉

检查字幕偏好

听力无障碍在空间计算中同样重要。visionOS 支持系统级的隐藏字幕(Closed Captioning)设置,App 应该检查这个偏好来决定是否显示字幕(23:35):

UIAccessibility.isClosedCaptioningEnabled
UIAccessibility.closedCaptioningStatusDidChangeNotification

关键点:

  • isClosedCaptioningEnabled 返回用户是否在系统设置中开启了隐藏字幕
  • 对于包含音频内容(对话、旁白、音效提示)的 App,应根据此值决定是否显示文字字幕
  • closedCaptioningStatusDidChangeNotification 让 App 在用户切换设置时实时更新
  • 字幕不仅帮助听障用户,也对在嘈杂环境或静音模式下使用设备的用户有帮助

核心启发

  1. 为 RealityKit 游戏添加完整的 VoiceOver 支持:如果你的 visionOS App 包含 3D 交互对象,为每个可交互的实体附加 AccessibilityComponent,设置 .label.value.traits.systemActions。为什么值得做:visionOS 上没有触摸屏,VoiceOver 是视障用户唯一的交互方式。不添加无障碍支持意味着这部分用户完全无法使用你的 App。怎么开始:找出 App 中所有可交互的 RealityKit 实体,参考上面的 AccessibilityComponent 代码片段逐个配置,然后用 VoiceOver 在模拟器中测试。

  2. 实现空间感知的事件播报:使用 AccessibilityNotification.Announcement 在关键场景变化时主动通知用户——比如”3 个敌人在你身后”、“门在你右侧打开了”。为什么值得做:空间应用中大量事件发生在用户视野之外。视障用户需要额外的听觉提示才能感知这些变化。怎么开始:梳理 App 中所有”发生但用户不一定看到”的事件,在事件触发点加上 .post() 调用,确保公告文本包含方位信息。

  3. 检测并适配 Head Anchor 偏好:在 App 启动时读取 accessibilityPrefersHeadAnchorAlternative,如果为 true,将头部跟随的 HUD、菜单、提示改为世界锚定或窗口内展示。为什么值得做:头部锚定内容持续占据视野边缘,对前庭敏感用户可能引发不适。提供替代方案可以大幅降低使用门槛。怎么开始:审查 App 中所有使用 head anchor 的 UI,为每种 UI 准备一个非头部锚定的 fallback 布局,通过 @Environment 值切换。

  4. 建立运动效果的 Reduce Motion 适配:读取 accessibilityReduceMotion,在值为 true 时将所有大幅动画替换为简洁过渡效果。为什么值得做:visionOS 的空间动画比平面屏幕更强烈——对象在三维空间中移动、旋转、缩放。对运动敏感的用户来说,这些效果可能直接导致无法使用。怎么开始:为 App 中的主要动画效果创建两个版本——完整版和简化版,通过环境变量选择版本,同时在 reduceMotionStatusDidChangeNotification 触发时动态切换。

  5. 为所有音频内容添加字幕支持:检查 isClosedCaptioningEnabled,在用户开启字幕时显示音频的文字描述。为什么值得做:visionOS 的空间音频体验虽然沉浸,但听障用户无法获取其中的信息。字幕是保证信息平等获取的关键。怎么开始:为 App 中的对话、旁白和关键音效准备文字描述,在播放音频时检查字幕偏好决定是否显示。

关联 Session

评论

GitHub Issues · utterances