Highlight
Apple 通过视障和听障开发者的真实案例说明,无障碍改进可以从按钮标签、VoiceOver 测试和多感官反馈开始,让同一个 App 被更多用户和开发者使用。
核心内容
很多团队把无障碍放到发布前检查。界面做完以后,再打开 VoiceOver 试一下。发现按钮读不出来、图片没有说明、交互依赖声音,就把它们列成以后再修的任务。
这场演讲给出的痛点更具体。一位开发者提到,有个 App 的图形按钮都没有标签,VoiceOver 用户无法知道按钮含义。开发者补上标签后,只用一天时间,App 就变得基本可访问。
Apple 没有把这场演讲做成 API 发布。它让视障和听障开发者讲自己的工作方式。听障开发者会自然检查 App 在没有声音时能否运行。视障开发者每天用屏幕阅读器写代码、测试 App、提交 bug。
这改变了无障碍的起点。开发者不用先设计一套单独版本。先做同一个版本,让按钮有名称,让 VoiceOver 能读出状态,让声音提示有可视替代,让视觉任务有音频引导。
从一个按钮标签开始
一个完全不可访问的 App,问题可能很小:图形按钮没有标签。屏幕阅读器只能读出“按钮”,用户不知道下一步会发生什么。
补上标签后,用户就能理解界面。这个改动不需要重写架构。它要求开发者把控件的语义交给系统。
工具本身也要可访问
听障开发者收到 VoiceOver bug 时,问题变成:如果听不到 VoiceOver 朗读,怎么测试?
答案是 VoiceOver 的字幕栏(caption bar)。它把 VoiceOver 朗读内容显示成文字,例如 “Tweets and replies, Button”。这个小功能让听障开发者也能调试 VoiceOver 问题。
从个人痛点做产品
Seeing AI 来自视障开发者的个人需要。它描述周围世界,读取文字,识别人和物体。扫描条形码时,团队用“热或冷”的方式提供音频引导:越接近目标,蜂鸣越快。
Cardzilla 来自听障开发者的打车经历。他原来用 Notes App 和司机沟通,但文字太小。于是做了一个把文字放大的 App,让对方更容易读,也让沟通更快。
详细内容
1. 给图形按钮提供可读标签
(02:05)演讲中的第一个具体案例是按钮标签。某个 App 因为图形按钮没有标签,VoiceOver 用户无法使用。开发者补上标签后,App 变得基本可访问。
SwiftUI 中,图形按钮可以用 accessibilityLabel 提供语义名称。下面是一个完整的最小示例。
import SwiftUI
struct PlaybackControls: View {
@State private var isPlaying = false
var body: some View {
Button {
isPlaying.toggle()
} label: {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
}
.accessibilityLabel(isPlaying ? "Pause" : "Play")
}
}
struct PlaybackControls_Previews: PreviewProvider {
static var previews: some View {
PlaybackControls()
}
}
关键点:
import SwiftUI引入 SwiftUI,示例可以直接放进 SwiftUI App 或预览中运行。@State private var isPlaying = false保存播放状态,按钮点击后会更新界面。Button { ... } label: { ... }创建一个只有图标的按钮。Image(systemName:)根据状态显示播放或暂停图标。.accessibilityLabel(isPlaying ? "Pause" : "Play")给图形按钮提供 VoiceOver 可朗读的名称。PlaybackControls_Previews提供 Xcode 预览入口,便于打开 Accessibility Preview 检查。
2. 用同一个版本服务所有用户
(01:43)Avid 的开发者说,目标不是做一个“盲人版 Pro Tools”。目标是一个所有人都能走上前使用的版本。
这个原则落到代码里,通常是把状态、动作和标签放在原有控件上,让系统辅助功能读取同一套界面。下面的示例把一个切换静音的按钮做成同一个控件,不额外复制一套无障碍界面。
import SwiftUI
struct MuteButton: View {
@State private var isMuted = false
var body: some View {
Button {
isMuted.toggle()
} label: {
Label(isMuted ? "Unmute" : "Mute",
systemImage: isMuted ? "speaker.slash.fill" : "speaker.wave.2.fill")
}
.accessibilityValue(isMuted ? "Muted" : "Sound on")
}
}
struct MuteButton_Previews: PreviewProvider {
static var previews: some View {
MuteButton()
}
}
关键点:
@State private var isMuted = false保存当前声音状态。Button是所有用户共用的交互入口。Label同时包含文字和系统图标,视觉用户和 VoiceOver 用户都能获得名称。systemImage随状态变化,视觉界面能表达静音状态。.accessibilityValue朗读当前值,让用户知道按钮现在对应“Muted”还是“Sound on”。
3. 让没有声音的用户也能检查 VoiceOver
(03:03)一位听障开发者收到 VoiceOver bug。他发现 VoiceOver 有字幕栏,可以把朗读内容显示成文字。演讲中的字幕栏显示了 “Tweets and replies, Button”“Media, Button”“Likes, Button”。
应用开发时,可以同时准备可朗读的标签和可见文本。这样听障测试者可以通过字幕栏核对 VoiceOver 输出,视障用户可以通过屏幕阅读器听到同一份语义。
import SwiftUI
struct ProfileTabs: View {
var body: some View {
HStack {
Button("Tweets and replies") {}
Button("Media") {}
Button("Likes") {}
}
.padding()
}
}
struct ProfileTabs_Previews: PreviewProvider {
static var previews: some View {
ProfileTabs()
}
}
关键点:
HStack横向排列三个按钮,对应演讲字幕栏中的三个可聚焦元素。Button("Tweets and replies") {}使用文字标题,VoiceOver 可以直接朗读。Button("Media") {}和Button("Likes") {}保持同样模式,减少遗漏标签的机会。.padding()只影响视觉布局,不改变无障碍语义。- 运行时打开 VoiceOver 字幕栏,测试者可以用文字核对朗读结果。
4. 为摄像头任务提供音频引导
(03:59)Seeing AI 团队发现,扫描条形码时用户需要知道相机是否对准目标。团队使用“热或冷”的游戏规则:越接近条形码,蜂鸣越快。
这个思路适合所有需要用户对准目标的任务。代码可以先把“接近程度”抽象成一个 0 到 1 的值,再把它转换成蜂鸣间隔。
import Foundation
struct BarcodeAudioGuide {
func beepInterval(for proximity: Double) -> TimeInterval {
let clampedProximity = min(max(proximity, 0), 1)
let slowestInterval = 1.0
let fastestInterval = 0.15
return slowestInterval - (slowestInterval - fastestInterval) * clampedProximity
}
}
let guide = BarcodeAudioGuide()
let interval = guide.beepInterval(for: 0.75)
print(interval)
关键点:
import Foundation引入TimeInterval。BarcodeAudioGuide保存条形码音频引导的计算逻辑。beepInterval(for:)接收接近程度,0表示远,1表示近。min(max(proximity, 0), 1)把输入限制在 0 到 1,避免异常值影响计算。slowestInterval是最慢蜂鸣间隔。fastestInterval是最快蜂鸣间隔。return根据接近程度缩短间隔,越接近目标,返回值越小。print(interval)让示例可以直接运行并观察结果。
5. 把文字放大,解决面对面沟通
(04:27)Cardzilla 的起点很日常:听障开发者坐出租车时,用 Notes App 给司机看文字,但文字太小。于是他做了一个放大文字、提高沟通效率的 App。
这个功能不复杂。关键是让输入内容立刻变成大字号、居中、便于对方阅读。
import SwiftUI
struct LargeMessageCard: View {
@State private var message = "Please take me home."
var body: some View {
VStack(spacing: 24) {
TextEditor(text: $message)
.frame(height: 120)
.border(Color.secondary)
Text(message)
.font(.system(size: 56, weight: .bold))
.multilineTextAlignment(.center)
.minimumScaleFactor(0.4)
.padding()
}
.padding()
}
}
struct LargeMessageCard_Previews: PreviewProvider {
static var previews: some View {
LargeMessageCard()
}
}
关键点:
@State private var message保存要展示给对方看的文字。TextEditor(text: $message)允许用户编辑内容。.frame(height: 120)限制输入区高度,让展示区保留空间。Text(message)显示放大后的内容。.font(.system(size: 56, weight: .bold))使用大字号和粗体,提高远距离可读性。.multilineTextAlignment(.center)让多行文字居中。.minimumScaleFactor(0.4)在文字过长时自动缩小,减少截断。
核心启发
-
做什么:给所有图标按钮补充可朗读名称。 为什么值得做:演讲中的案例显示,缺少按钮标签会让 App 完全不可访问;补上标签后,一天内就能让 App 基本可用。 怎么开始:打开常用页面,逐个检查只有图标的
Button,在 SwiftUI 中添加.accessibilityLabel,在 UIKit 中设置accessibilityLabel。 -
做什么:给语音和声音提示增加可视检查路径。 为什么值得做:听障开发者依靠 VoiceOver 字幕栏调试 VoiceOver bug,说明测试工具和调试信息需要跨感官呈现。 怎么开始:打开 VoiceOver 字幕栏,核对每个可聚焦元素读出的文字;对依赖音频的状态提示增加文字、图标或震动反馈。
-
做什么:为摄像头扫描增加“越接近越密集”的反馈。 为什么值得做:Seeing AI 的条形码扫描用蜂鸣频率帮助用户对准目标,解决了看不到取景画面时的操作问题。 怎么开始:先计算目标在画面中的置信度或距离,把它映射成蜂鸣间隔、震动节奏或屏幕提示。
-
做什么:做一个面对面大字沟通模式。 为什么值得做:Cardzilla 的场景来自出租车沟通,核心需求是让对方快速读懂文字。 怎么开始:用
TextEditor输入内容,用大字号Text全屏展示,再加入语音输入入口。 -
做什么:把残障用户加入 beta 测试流程。 为什么值得做:演讲提到,无障碍需要公司内部和外部 beta 测试者的输入;团队多样性会带来更直接的问题发现。 怎么开始:在 TestFlight 招募说明中明确欢迎 VoiceOver、字幕、键盘访问、Switch Control 用户反馈,并把反馈归类到具体页面和控件。
关联 Session
- SwiftUI Accessibility: Beyond the basics — 学习 SwiftUI 无障碍修饰器、分组、焦点和 rotor 的具体实现。
- Support Full Keyboard Access in your iOS app — 让 iOS 应用可以只靠键盘完成导航和操作。
- Tailor the VoiceOver experience in your data-rich apps — 为数据密集型界面提供更精简、更可展开的 VoiceOver 信息。
- Bring accessibility to charts in your app — 把图表转换成 VoiceOver 可导航、Audio Graph 可播放的数据结构。
- The process of inclusive design — 把包容性设计纳入团队建设、构思、开发和测试流程。
评论
GitHub Issues · utterances