Swift & UI 进阶 20m
用 SwiftUI 和 UIKit 构建无障碍应用
Build accessible apps with SwiftUI and UIKit
2023年6月5日
一句话判断
如果你的 App 还没认真做过无障碍适配,这场 Session 提供的 API 足够让你低成本地迈出一大步——特别是 isToggle trait 和 AccessibilityNotification 这两个新增能力。
这场 Session 讲了什么
这场 Session 围绕 SwiftUI 和 UIKit 的无障碍新特性展开,聚焦三个方向:新的交互 API、视觉无障碍增强、以及 UIKit 属性同步的改进。
开场用一个照片编辑 App 作为贯穿全场的示例场景。核心议题包括:
- isToggle trait:自定义按钮现在可以声明为 toggle,VoiceOver 会自动给出”switch button”的描述和”双击切换设置”的提示,无需手动添加 accessibility hint。
- AccessibilityNotification:全新的 Swift 原生通知 API,统一了 SwiftUI、UIKit、AppKit 三个平台的公告(announcement)、布局变化、屏幕切换、页面滚动通知。最大的亮点是支持公告优先级——high、default、low 三级,high 可以打断当前语音且自身不可被打断,low 会排队等待。这解决了多个公告同时触发时互相干扰的老问题。
- Accessibility Zoom Action:为图片等可缩放视图添加无障碍缩放支持,VoiceOver 用户可以通过手势完成缩放操作,不再依赖物理触摸捏合。
- Direct Touch(allowsDirectInteraction):允许指定屏幕区域让 VoiceOver 手势直接穿透到 App,同时可选静音模式。典型场景是钢琴键盘——用户需要快速连续按键,VoiceOver 的朗读反而成了障碍。
- SwiftUI 视觉无障碍:包括 accessibilityLabeledPair(将标签与控件配对)、accessibilityElement(children:) 的增强、以及新的文本样式适配动态字体。
- UIKit 属性自动同步:新的 accessibility properties 使用 property observer 模式,当 UI 状态变化时自动更新无障碍属性,不再需要手动在各处同步。
值得深挖的点
公告优先级机制是这次最值得关注的改进。之前多个 announcement 同时触发时,VoiceOver 的朗读顺序基本不可控,这在导航类 App 中是个常见痛点。现在通过 attributed string 设置优先级,可以精确控制用户听到什么、忽略什么。这背后的设计思路是:不是所有信息都同等重要。
Direct Touch 的静音模式也值得仔细研究。它让 VoiceOver 在特定区域只做触摸转发不做朗读,这对乐器类 App、绘图类 App、游戏来说几乎是必需的功能。之前这类 App 的 VoiceOver 体验普遍很差,现在有了官方解决方案。
UIKit 属性自动同步虽然不是性感的新功能,但对维护大型 UIKit 项目来说是实打实的工程改进。以前每个状态变化点都要手动更新 accessibility label/value/traits,遗漏几乎是必然的。
代码片段
SwiftUI 中添加 isToggle trait:
struct FilterButton: View {
@State private var filterOn = false
var body: some View {
Button {
filterOn.toggle()
} label: {
// 按钮外观
}
// 添加 isToggle trait,VoiceOver 自动识别为开关按钮
.accessibilityAddTraits(.isToggle)
}
}
带优先级的无障碍公告:
// 创建带优先级的 attributed string
var lowPriorityString: AttributedString {
var str = AttributedString("正在加载相机")
str.accessibilitySpeechAnnouncementPriority = .low
return str
}
var highPriorityString: AttributedString {
var str = AttributedString("相机已就绪")
str.accessibilitySpeechAnnouncementPriority = .high
return str
}
// 按优先级发送公告
AccessibilityNotification.Announcement("正在打开相机").post()
AccessibilityNotification.Announcement(lowPriorityString).post()
AccessibilityNotification.Announcement(highPriorityString).post()
// 结果:低优先级不会打断默认公告,高优先级会打断所有
SwiftUI 无障碍缩放操作:
struct ZoomingImageView: View {
@State private var scale: CGFloat = 1.0
var body: some View {
Image("photo")
.scaleEffect(scale)
// 添加无障碍缩放操作
.accessibilityZoomAction { direction in
switch direction {
case .zoomIn:
scale += 1.0
case .zoomOut:
scale = max(1.0, scale - 1.0)
}
AccessibilityNotification.Announcement("\(Int(scale))x 缩放").post()
}
}
}
最佳实践
- 给自定义开关控件加上
isToggletrait,别再手动拼 accessibility hint 了。系统提供的描述比你自己写的更一致。 - 公告优先级不要滥用。high 优先级意味着”用户必须听到这条信息”,大部分场景用 default 就够了。把 high 留给安全相关或状态关键变更的提示。
- Direct Touch 区域要做明确的无障碍标注。虽然该区域静音了,但用户第一次探索时需要知道这里可以做什么。
- UIKit 项目中,把旧的 accessibility 属性迁移到新的 property observer 模式。这是一个低风险高回报的重构——减少手动同步出错的可能。
- 无障碍缩放操作记得返回缩放倍数的语音反馈,否则用户不知道当前缩放到什么程度了。
还有什么值得关注
AccessibilityNotification是跨平台 API,AppKit 也适用,macOS App 可以同步受益。accessibilityLabeledPair把标签和控件的关系显式声明,VoiceOver 可以在用户聚焦控件时自动朗读关联标签,对复杂表单场景很有帮助。- 这场 Session 没有深入讨论 Switch Control 和 Voice Control 的适配,如果你的 App 需要全面无障碍支持,还需要结合其他 Session 的内容。
- SF Symbols 5 的动画效果(见 Session 10258)也可以用来增强无障碍反馈——比如用 bounce 动画指示按钮被按下。
WWDC 2023