Highlight
Mac Catalyst 会自动转换 iOS 无障碍信息,但开发者仍要补齐 Mac 上的键盘焦点、快捷键、语义分组标签,并用 Accessibility Inspector 验证 UIKit 无障碍属性在 macOS 上的映射。
核心内容
把一个已经可访问的 iPad App 带到 Mac,并不等于无障碍工作结束。UIKit 会把很多 iOS 无障碍信息转换到 Mac Catalyst,但用户在 Mac 上使用 App 的方式变了:键盘是主要输入设备,VoiceOver 依靠键盘在元素树中移动,桌面界面也常常比 iPad 上更密集。
Session 用 Roasted Beans 这个咖啡示例 App 展开。这个 App 已经在 iOS 上做过无障碍适配,现在要通过 Mac Catalyst 运行在 macOS。刚开始,UIKit 已经能让按钮、侧边栏和工具按钮进入 Tab 焦点循环;如果用户按方向键切换 table view 选项,有些列表还需要开发者显式开启新的 selectionFollowsFocus API。
键盘只是第一层。Mac 用户还期待常用动作有快捷键,例如分享、添加、评分。对于依赖辅助技术的用户,反复寻找屏幕上的按钮会增加负担。把高频操作放进菜单栏和快捷键,可以减少导航成本,也能回馈到 iOS 的 Full Keyboard Access。
第二层是 VoiceOver 导航效率。iOS 上用户可以用触摸快速跳到屏幕上的某个元素;Mac 上没有这个捷径,VoiceOver 用户要用键盘穿过 accessibility tree。如果一个界面暴露 26 个可访问元素,用户可能要按很多次键才能到达目标。accessibilityContainerType 让相关元素形成分组,Mac Catalyst 会把容器当作树上的独立节点,用户可以先跳过整个组,再决定是否进入组内细节。
最后是测试。开发者仍然写 UIKit 无障碍 API,但 App 运行在 macOS 上。WWDC 2020 的 Accessibility Inspector 新增 Catalyst 区域,会显示 iOS 的 traits、container type、automation type 和 UIKit 视图控制器信息。它能帮助开发者确认转换后的 Mac 无障碍树是否符合预期。
详细内容
1. 让列表选择跟随键盘焦点
(03:58)UIKit 默认让很多控件参与键盘焦点循环。对于 UITableView 或 UICollectionView,如果方向键没有同步改变选择,Session 给出的入口是 selectionFollowsFocus。
myTableView.selectionFollowsFocus = true
关键点:
selectionFollowsFocus让方向键移动焦点时自动更新 table view 或 collection view 的选择。UISplitView侧边栏里的 table view 会由 UIKit 自动开启这个行为。- 普通列表需要开发者根据界面意图手动开启。
- 这个设置解决的是 Mac 上“焦点到了,选择没跟上”的断裂感。
2. 把高频动作接入菜单栏和快捷键
(05:56)示例 App 希望给分享操作添加 Command-I。实现路径是覆写 AppDelegate.buildMenu(with:),创建一个 UIKeyCommand,再把它作为 UIMenu 的 child 插入菜单栏。
extension AppDelegate {
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
let shareCommand = UIKeyCommand(title: NSLocalizedString("Share", comment: ""),
action: #selector(Self.handleShareMenuAction),
input: "I",
modifierFlags: [.command])
let shareMenu = UIMenu(title: "",
identifier: UIMenu.Identifier("com.example.apple-samplecode.RoastedBeans.share"),
options: .displayInline,
children: [shareCommand])
builder.insertChild(shareMenu, atEndOfMenu: .edit)
}
@objc func handleShareMenuAction() {
}
}
关键点:
buildMenu(with:)是 Mac Catalyst 菜单栏定制入口。UIKeyCommand的title会显示在菜单项里,所以要使用本地化字符串。input: "I"和modifierFlags: [.command]对应 Command-I。action指向实际处理分享的 selector。UIMenu使用.displayInline,让单个分享命令直接显示在目标菜单位置。builder.insertChild(..., atEndOfMenu: .edit)把命令放到 Edit 菜单末尾。
(07:16)Session 还提到 iOS 13.4 起可以从 UIPress 读取原始 key code。游戏或更底层的键盘控制可以直接响应具体按键。
extension MyViewController {
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
switch presses.first?.key?.keyCode {
case .keyboardLeftGUI:
// Handle command key pressed
case .keyboardB:
// Handle B key pressed
default:
}
}
}
关键点:
pressesBegan(_:with:)在按键按下时进入 responder。presses.first?.key?.keyCode可以读取具体键位。.keyboardLeftGUI对应左 Command 键。.keyboardB对应 B 键。- 这类原始事件适合游戏或需要完整键盘控制权的界面;普通菜单命令优先使用
UIKeyCommand。
3. 用 accessibilityContainerType 降低 VoiceOver 导航成本
(09:00)VoiceOver 会根据 accessibility tree 读取界面。iOS 模型里,可访问元素通常是一层 leaf node;Mac 上用户用键盘逐项移动,复杂界面会暴露太多连续节点。Session 的 Roasted Beans 示例里,分组前有 26 个可见可访问元素。
(10:49)accessibilityContainerType 让元素之间建立关系。Mac Catalyst 会把容器本身放进 accessibility tree,容器里的元素成为子树。用户可以先聚焦容器,选择跳过整个容器,或进入容器内部继续导航。
(15:28)标准 UITableView 和 UICollectionView 默认已经是 semanticGroup。开发者需要给这些容器补上清晰的本地化标签。
tableView.accessibilityLabel = NSLocalizedString("Coffee list", comment: "")
关键点:
UITableView本身已经是一个语义分组容器。- Mac Catalyst 会让容器成为可聚焦的独立节点。
- 容器需要有自己的
accessibilityLabel,否则 VoiceOver 聚焦容器时缺少上下文。 - 标签应该描述这个组的内容,例如 “Coffee list”。
(15:50)如果容器带有状态,标签还应该包含关键状态。示例在选择某个咖啡后,把当前品牌名写进 table view 的可访问标签。
extension RBListViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let data = tableData[indexPath.row]
let label = NSLocalizedString("Coffee list", comment: "")
let selectedLabel = NSLocalizedString("%@ selected", comment: "")
tableView.accessibilityLabel = label + ", " + String(format: selectedLabel, data.coffee.brand)
}
}
关键点:
didSelectRowAt是更新容器状态标签的合适位置。label描述容器是什么。selectedLabel描述容器当前选择了什么。String(format: selectedLabel, data.coffee.brand)把业务状态接进可访问文案。- 这样 VoiceOver 用户聚焦列表容器时,能先听到列表名称和当前选择。
4. 手动把自定义信息区做成语义分组
(16:21)Roasted Beans 右侧详情区不是 table view,也不是 collection view。可用地点被实现成一个垂直 UIStackView,里面有多个 UILabel。如果保持原状,VoiceOver 用户要逐个穿过这些标签。Session 用 accessibilityContainerType = .semanticGroup 把它变成一个可跳过或可进入的组。
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
let locationsAvailable = viewModel.locationsAvailable
let titleLabel = UILabel()
titleLabel.font = UIFont.preferredFont(forTextStyle: .body).bold()
titleLabel.text = NSLocalizedString("Availability: ", comment: "")
stackView.addArrangedSubview(titleLabel)
for location in locationsAvailable {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .body)
label.text = "• " + location
label.accessibilityLabel = location
stackView.addArrangedSubview(label)
}
stackView.accessibilityLabel = String(format: NSLocalizedString("Available at %@ locations", comment: ""), String(locationsAvailable.count))
stackView.accessibilityContainerType = .semanticGroup
关键点:
UIStackView负责承载一组视觉上相关的可用地点。- 每个
UILabel仍然有自己的accessibilityLabel,进入组后可以逐项读取。 stackView.accessibilityLabel给整个组一个摘要。- 摘要使用地点数量,避免用户先听完所有地点才知道这个区域的规模。
.semanticGroup是 iOS 上通用的容器类型;Mac Catalyst 会把它映射成可聚焦的分组节点。- 分组过多会让内部元素变难发现,所以适合功能区、同类项目或口头描述时自然会归为一组的区域。
5. 用 Accessibility Inspector 检查 Catalyst 映射
(18:48)Session 最后演示了 Accessibility Inspector。Mac Catalyst App 运行在 macOS 上,但开发者写的是 UIKit 无障碍 API。新版 Inspector 会在 Catalyst 区域显示 iOS API 侧的信息。
检查 cell 时,Inspector 能显示元素 label 和 value。继续用 Command-Control-Up 检查父级时,它会定位到 table view 容器,显示容器类型、UIKit 类转换后的 Mac 平台元素、所在 view controller,以及 automation type。开发者可以用这些信息确认 accessibilityLabel 和 accessibilityContainerType 是否真的进入了 macOS 无障碍树。
核心启发
-
做什么:给 Mac Catalyst App 做一次键盘焦点审计。 为什么值得做:Session 明确说 macOS 上键盘是主要交互方式,所有可交互控件都应该能获得键盘焦点。 怎么开始:在系统设置中开启“Use keyboard navigation to move focus between controls”,用 Tab、方向键和空格遍历核心页面;对没有随焦点改变选择的
UITableView或UICollectionView设置selectionFollowsFocus = true。 -
做什么:把高频操作做成菜单命令和键盘快捷键。 为什么值得做:对辅助技术用户来说,寻找屏幕按钮会拖慢操作;快捷键能让分享、添加、评分这类动作直接触达。 怎么开始:在
AppDelegate.buildMenu(with:)中创建UIKeyCommand,设置本地化title、action、input和modifierFlags,再通过UIMenuBuilder插入合适的菜单。 -
做什么:为复杂详情页设计 accessibility group。 为什么值得做:Mac 上 VoiceOver 用户用键盘逐项导航,分组可以把长列表式的 accessibility tree 变成更接近 AppKit 的层级树。 怎么开始:按功能区识别自然分组,例如详情摘要、可用地点、过滤条件;给容器设置本地化
accessibilityLabel,再把accessibilityContainerType设为.semanticGroup。 -
做什么:让容器标签包含当前状态。 为什么值得做:容器在 Mac Catalyst 中会成为独立可聚焦节点,用户聚焦时需要听到组名和关键状态。 怎么开始:在选择变化、筛选变化或数据刷新后更新容器的
accessibilityLabel,例如把列表名称和当前选中项拼成一个本地化句子。 -
做什么:把 Accessibility Inspector 加进 Mac Catalyst 回归测试。 为什么值得做:Inspector 能显示 Catalyst 映射后的 traits、container type、automation type 和 UIKit view controller,适合确认 UIKit 无障碍 API 在 macOS 上的实际结果。 怎么开始:针对核心页面检查子元素和父容器,确认 cell 有 label/value,table view 或自定义容器有 label 与
semanticGroup,并记录 automation type 供 UI 测试定位使用。
关联 Session
- What’s new in Mac Catalyst — 了解 Mac Catalyst 的生命周期、扩展、macOS 外观和 Optimized for Mac 模式。
- Support hardware keyboards in your app — 讲解 iPadOS 与 Mac Catalyst App 如何通过 responder chain、UIKeyCommand 和原始键盘事件支持硬件键盘。
- Handle trackpad and mouse input — 处理 iPad 与 Mac Catalyst 中的触控板、鼠标、滚动、修饰键和间接输入事件。
- VoiceOver efficiency with custom rotors — 用自定义 Rotor 帮助 VoiceOver 用户在复杂界面中按 App 特有维度快速导航。
- Make your app visually accessible — 从颜色、文字大小、对比度和视觉设置角度改进 App 的可访问性。
评论
GitHub Issues · utterances