-
优化自定控件的辅助功能体验
完善 App 的交互式元素,让人人都能方便使用,从而充分释放这些元素的潜力。我们将详细讲解用户如何借助旁白及其他辅助技术来理解并使用控件;还将介绍多种输入方法,例如操作、透视手势和直接触控。与我们一起深入探索多个示例控件,优化提升每个控件的辅助功能体验。
章节
- 0:01 - Introduction
- 1:02 - Guiding principles
- 8:41 - Complex controls
资源
相关视频
WWDC24
-
搜索此视频…
你好 我是Khin 是无障碍团队的软件工程师 自定义UI控件让用户能够在你的App中 做出独特而富有创意的事情 它们让用户能够使用手势 以及超越标准控件的各种交互方式 在Apple 我们相信技术服务所有人时 才能发挥最大效用 这意味着为使用辅助技术的用户 带来出色的体验 例如VoiceOver Switch Control等 让控件支持无障碍访问 确保所有人都能使用 你的App所提供的核心功能 在本节中 我将探讨如何让App中的 任何控件都支持无障碍访问 首先 我将介绍一些 你可以参考的指导原则 然后展示如何将这些原则 应用于几个复杂的控件 现在来谈谈这些指导原则 来看这个滑块控件 这是SwiftUI中的标准滑块 即使你是第一次看到它 你可能已经从外观上 了解了很多关于它的信息 它包含一条轨道 以及轨道上的一个滑块手柄 手柄大约位于轨道的中间位置 手柄看起来也像是可以被抓取的 因此可以通过拖动来移动 通过这些视觉信息 你可以获得很多关于该控件的隐性提示 你可以判断出这个控件 表示一个连续的值 你也知道当前的值是多少 在本例中 大约是最大值的一半 你也能识别出操作手势 例如如何改变其值 手柄看起来可以拖动 当你拖动时 反馈是即时的 轨道填充 手柄移动 清晰地反映了操作已发生 这一切无需任何解释 一眼即可理解 你的大脑处理了形状 位置 以及预期行为 这一切都在瞬间完成 这就是视觉信息的强大之处 那么 如果有人看不到屏幕呢 轨道 手柄 位置 这些都无从得知 并非每个人都能获取视觉信息 因此有必要考虑 使用辅助技术的用户将如何 与此类控件进行交互
以下是VoiceOver 描述这个滑块的方式 VoiceOver是Apple平台上 内置的屏幕阅读器 它让视障或低视力用户 能够通过手势与设备交互 "亮度 50% 可调整" "用单指向上或向下轻扫 以调整值" 通过VoiceOver 用户能够了解 滑块所控制的内容 标签显示为"亮度" 还会提示该控件有一个值 且当前设置为50% 用户可以采取的操作 也一目了然 它显示为可调整 并提示如何调整值 当值发生变化时 会实时播报新的值 这就是用户获得的反馈 VoiceOver用户可以获得出色的体验 即便无法看到控件的视觉效果
思考用户从控件视觉形式中 所能获取的信息 将这些信息作为你的指导原则 来构建自己的无障碍体验 确保控件的用途清晰易懂 若控件表示一个值 请将该值提供给辅助技术 明确用户可以采取的操作 以及如何获得反馈 在使用控件的过程中
这是另一个示例 我咖啡机App中的一个自定义控件 有些早晨需要满满一杯 有些早晨只需半杯 这取决于我是被闹钟叫醒 还是被嚎叫的猫吵醒 于是 我开发了一款App 让我能控制每次冲泡的咖啡量 只需一个简单的手势 向上拖动增加咖啡量 向下拖动减少咖啡量 填充量表示要冲泡的盎司数 目前 这个控件没有提供 任何额外的无障碍信息 来支持无障碍访问 当我向右轻扫选中控件时 VoiceOver无法描述其功能 或用户如何更改其值 "设置" "按钮"
"6盎司" "在杯子上向上或向下拖动" 目前尚不清楚 如何与此控件进行交互 不过别担心 只需几个简单步骤即可改进 为此 我将重新回顾 之前介绍的指导原则 以下是CoffeeDispenserView的实现代码 目前 它仅将咖啡量声明为State 并传递给滑块 首先 我将把滑块 标记为无障碍元素 为赋予其明确用途 我使用accessibilityLabel修饰符 将其命名为"Coffee Dispenser" 然后使用 accessibilityValue修饰符 来播报当前的填充量 我还想让VoiceOver能够 调整咖啡滑块 与内置滑块的方式相同 我将为这个控件添加相同的交互方式 首先添加一个 名为.adjustable的特征 这会告知VoiceOver 该控件可以通过轻扫来调整 然后定义每次轻扫的操作 使用.accessibilityAdjustableAction修饰符 该闭包提供一个方向参数 值为.increment或.decrement 闭包应处理每种情况
完成这些更改后 这是全新的VoiceOver体验 向右轻扫选中控件 然后向上和向下轻扫 以调整所需的咖啡量 "Coffee dispenser 6盎司 可调整" "用单指向上或向下轻扫 以调整值"
"7盎司" "8盎司" "7盎司" "6盎司" 用户现在可以使用VoiceOver 每次以1盎司为单位调整咖啡量
但如果有人更挑剔 想要以半盎司为单位调整呢 精确控制方面 VoiceOver内置了一种称为 穿透手势的功能
要执行穿透手势 VoiceOver用户 可以双击并按住以激活 手势从控件的 accessibilityActivationPoint开始 当用户的手指移动时 VoiceOver会将触摸事件 直接发送给控件 这使用户能够对值进行 更精确细致的控制 对于咖啡滑块 无障碍激活点 默认位于中心位置 我将把它设置为 与当前填充量相匹配 这样 手势始终从 咖啡液面处开始 为向任意方向调整 留出操作空间
在穿透手势期间为用户 提供反馈也很重要 为此 我会发布 无障碍通知 当值发生变化时 但不是每次变化都播报 那样会太嘈杂 我会改为记录 上次播报的内容和时间 如果值确实发生了变化 且已过了至少0.3秒 那么就播报 否则跳过 这样 VoiceOver用户 就能听到有意义的更新
现在我将双击并按住 然后向上移动手指 尝试一个穿透手势 将咖啡量填充至9.5盎司 "Coffee dispenser 6盎司 可调整" "用单指向上或向下轻扫 以调整值"
"六... 6.4盎司" "六... 六... 七... 七... 七点... 8.3盎... 8.4盎司" "8.5盎... 8.6盎司" "8.7盎司" "8.8盎... 9... 9.5盎司"
太棒了 控件现在可以拖动 并能提供有意义的更新 体验现在达到了 我之前分享的指导原则 所提出的要求 包含标签 值 操作和通知 现在我已经探讨了 基本的自定义控件 接下来看一些更复杂的示例 这是我在iOS上 最喜欢的功能之一 背景声音 让我可以播放环境音频 例如雨声或海浪声 帮助我集中注意力和放松 你可以直接在 无障碍设置中找到它 设置中有一个均衡器控件 这是一个二维面板 你可以将中心的手柄 移动到面板上的任意位置 这个面板有两个维度 可以移动来调整音调 视觉上 每个轴都有 频率和振幅符号 当你抓住手柄时 实际上可以探索 这两个值的空间 同时进行调整 对于滑块 可调整特征 提供了两种操作 递增和递减 在单个轴上 但对于这个控件 有两个轴 水平和垂直 添加可调整特征 只能覆盖一个方向 因此这不是理想的解决方案 这正是自定义操作 发挥作用的地方 自定义操作让你能够 公开控件的常用操作 每个操作都有一个标签 VoiceOver会朗读出来 以及激活时运行的闭包 与可调整操作不同 自定义操作支持 你定义的任何操作 不限于单个轴 对于这个均衡器面板 以下是添加自定义操作的方式 在均衡器面板视图上 accessibilityAction修饰符 被添加了四次 每个操作都有一个描述性名称 向上移动 向右移动 向下移动 向左移动 每个操作以固定步长 移动单个轴 在此范围内限制 这些操作让用户 能够探索整个空间 只需执行那些已被人们 所熟悉的操作 依赖辅助技术的用户
以下是使用自定义操作 操作均衡器面板的体验 我将向上和向下轻扫以选择操作 然后双击执行 "滤波器图表 频率0 振幅10" "双击并按住 然后拖动以调整滤波器" "图表轴的范围 设置为-100至100" "向上或向下轻扫以 选择自定义操作" "然后双击以激活" "向下移动" "向上移动" "向右移动" "向左移动" "向右移动" "向上移动" "频率0 振幅20" "频率0 振幅30" "频率0 振幅40"
用户可以通过操作 在2D空间中导航 声音本身提供了清晰的反馈
均衡器面板是一个很好的示例 展示了平台如何 将每项指导原则应用于 自身的体验设计 现在 我想向你展示 我一直在开发的一款App 它有自己的自定义控件 如果你和我一样 一定很爱你的宠物 当你外出上班时 真的会很想念它们 为了让我在白天有伴 我开发了一款App 让我能与我的猫互动 就在屏幕上 抚摸它 它会发出呼噜声 点击它 它会喵喵叫 捏它 嗯 它是猫嘛 当然会嘶嘶地回击你 就像真实的猫一样 我还没有为这个控件 添加任何无障碍支持 我将查看VoiceOver的 默认体验 "Cat fill 空格 图像" "触摸猫咪进行互动" 只是说屏幕上有一张图片 抚摸 点击和捏合 是该控件支持的所有手势 但VoiceOver不知道它们的存在
所有这些特色 呼噜声 揉捏声 愤怒的喵喵声 这些都在那里 等待着与依赖辅助技术的用户分享 VirtualCat是一个SwiftUI视图 包含互动猫咪界面 为了表达控件的用途 我添加了一个无障碍标签 内容为"Virtual Cat" 然后将无障碍值设置为 猫咪当前的反应 现在我需要一种方式 向VoiceOver展示与猫互动的手势 用户可以使用穿透手势 但这里可能不是最佳选择 例如 用户可能希望 反复执行某个操作 或使用多种手势 因此对于这个控件 我将使用直接触摸 直接触摸API将屏幕的某个区域 标记为直接触摸区域 当你添加 allowsDirectInteraction特征时 触摸事件会直接传递给控件 而不是由VoiceOver处理 这样 用户可以直接与控件交互 从而使用所有 该控件支持的手势 你可以使用直接触摸选项 自定义此行为 第一个选项是.requiresActivation 设置后 控件不会 响应直接触摸 直到用户双击 这允许用户 在屏幕上拖动手指 而不会意外激活它 与穿透手势不同 直接触摸会保持活动状态 直到焦点移至另一个元素 另一个选项是.silentOnTouch 设置后 VoiceOver 会保持完全静默 当用户触摸该区域时 这适用于提供 自身音频反馈的控件 在这种情况下VoiceOver的语音 会覆盖控件本身的音频 为使此控件可交互 我将添加 .accessibilityDirectTouch修饰符 并使用.requiresActivation选项
请注意 并非所有人 都能执行直接触摸手势 因此 尽可能提供 其他与控件交互的方式 例如使用自定义操作
以下是更新后的VoiceOver体验 我可以双击以激活直接触摸 然后尝试抚摸 点击 和捏合猫咪
"Virtual cat 睡觉中" "激活以开始 直接触摸交互" "操作可用"
"Virtual cat" "睡觉中"
完成这些更改后 VoiceOver用户 了解了控件的描述 以及执行直接触摸的手势 他们还能从交互中获得反馈 这将这款App中 所有令人愉快的体验 带给了使用辅助技术的用户
让你自己的 自定义交互控件 对所有人都可访问 打开VoiceOver 启动你的App 找到改进的机会 构建自定义控件时 考虑用户能否理解 控件的用途 值 操作和反馈 当你的控件依赖手势时 考虑使用直接触摸 而穿透手势 并不最适合的情况下 并尽可能提供自定义操作 这样 使用Switch Control Voice Control 以及其他辅助技术的用户 都能进行相同的交互 感谢收看
-
-
5:01 - Improve accessibility for coffee dispenser
// Improve accessibility for coffee dispenser import SwiftUI struct CoffeeDispenserView: View { @State var coffee: Double = 0.0 var body: some View { CoffeeSlider(value: coffee) .accessibilityElement() .accessibilityLabel("Coffee Dispenser") .accessibilityValue("\(Int(coffee)) ounces") .accessibilityAddTraits(.adjustable) .accessibilityAdjustableAction { direction in switch direction { case .increment: increaseCoffeeAmount() case .decrement: decreaseCoffeeAmount() } } } } -
7:05 - Set the accessibility activation point
// Set the accessibility activation point import SwiftUI struct CoffeeDispenserView: View { @State var coffee: Double = 0.0 var body: some View { CoffeeSlider(value: coffee) .accessibilityActivationPoint( UnitPoint(x: 0.5, y: 1 - coffee) ) } } -
7:27 - Post accessibility announcements
// Post accessibility announcements import SwiftUI struct CoffeeDispenserView: View { @State var coffee: Double = 0.0 var body: some View { CoffeeSlider(value: coffee) // ... .onChange(of: coffee) { _, newValue in if sufficientTimeSinceLastAnnouncement() && valueHasChanged() { cacheLastSpokenValue(newValue) AccessibilityNotification .Announcement(newValue) .post() } } } } -
10:13 - Add custom actions
// Add custom actions import SwiftUI struct EqualizerView: View { var body: some View { EqualizerPad() .accessibilityActions("Move Up") { increaseY(by: 10) } .accessibilityActions("Move Right") { increaseX(by: 10) } .accessibilityActions("Move Down") { decreaseY(by: 10) } .accessibilityActions("Move Left") { decreaseX(by: 10) } } } -
12:47 - Customize accessibility for the interactive cat surface
// Customize accessibility for the interactive cat surface import SwiftUI struct VirtualCat: View { var cat: CatModel var body: some View { InteractiveCatSurface() .accessibilityLabel("Virtual Cat") .accessibilityValue(cat.currentReaction.description) .accessibilityDirectTouch([.requiresActivation]) } }
-
-
- 0:01 - Introduction
Why custom controls need accessibility support so everyone can use what your app was built to do, and what the session covers — guiding principles and how to apply them to complex controls.
- 1:02 - Guiding principles
Create accessible custom controls by translating implicit visual cues into explicit information for assistive technologies. Apply labels, values, traits, and actions to ensure these controls are universally understood and actionable by everyone.
- 8:41 - Complex controls
Complex controls, like multi-dimensional pads and highly interactive virtual surfaces, demand advanced accessibility techniques beyond basic labels and values. Implementing features like passthrough gestures, custom actions, and the Direct Touch API allows people to seamlessly navigate and interact with these interfaces.