Accessibility design for Mac Catalyst
Accessibility 进阶 18m

Mac Catalyst 的无障碍设计

Accessibility design for Mac Catalyst

2020年6月25日

在 Apple 官方观看视频

一句话判断

Mac Catalyst App 的无障碍支持不是”自动就能工作”的——macOS 有自己的 VoiceOver 行为、键盘导航模式和高对比度需求,你需要专门为 Mac 环境做无障碍适配。

这场 Session 讲了什么

Mac Catalyst 让 iOS App 可以在 macOS 上运行,但在无障碍方面有一些特殊的注意事项。Session 详细讲解了 iOS 和 macOS 在无障碍上的差异。

主要差异包括:macOS 的 VoiceOver 使用不同的手势模型(键盘为主而非触摸);macOS 期望所有可交互元素都支持键盘操作(Tab 导航和 Enter 激活);macOS 的高对比度模式与 iOS 的实现不同;拖拽操作在 Mac 上需要支持鼠标。Session 还介绍了如何使用 Accessibility Inspector 来测试 Mac Catalyst App 的无障碍支持,以及如何在 iOS 和 macOS 之间共享无障碍配置。

值得深挖的点

键盘导航是 Mac 的基本要求

在 iOS 上,VoiceOver 用户通过触摸屏幕来导航。在 macOS 上,用户通过 Tab 键和方向键来导航。你的 Mac Catalyst App 必须支持完整的键盘操作路径。这意味着每个可交互元素都需要设置 isAccessibilityElement = true 和正确的 accessibilityTraits。系统会自动将 UIKit 的焦点系统映射为 macOS 的键盘焦点,但自定义视图需要额外的工作。

macOS 的 VoiceOver 差异

macOS 的 VoiceOver 有一些 iOS 没有的行为。比如 VO 键(Control + Option)组合是 macOS VoiceOver 的修饰键。VO + 空格相当于 iOS 的双击。VO + 左右箭头在元素之间导航。这些映射大部分是自动的,但如果你使用了自定义手势识别器,需要确保它们在 Mac 上有对应的键盘操作。

代码片段

为 Mac Catalyst 配置键盘导航

场景:确保所有按钮和交互元素支持 Tab 导航。

import UIKit

#if targetEnvironment(macCatalyst)

class MacAccessibleView: UIView {
    
    private var submitButton: UIButton!
    private var cancelButton: UIButton!
    private var textField: UITextField!
    
    func setupAccessibility() {
        // 确保所有交互元素都是可访问的
        submitButton.isAccessibilityElement = true
        submitButton.accessibilityLabel = "提交"
        submitButton.accessibilityTraits = .button
        
        cancelButton.isAccessibilityElement = true
        cancelButton.accessibilityLabel = "取消"
        cancelButton.accessibilityTraits = .button
        
        textField.isAccessibilityElement = true
        textField.accessibilityLabel = "输入内容"
        textField.accessibilityTraits = .keyboardKey  // 表示接受文本输入
    }
    
    // 设置 Tab 导航顺序
    override var accessibilityElements: [Any]? {
        get {
            // 定义 Tab 键的导航顺序
            return [textField, submitButton, cancelButton]
        }
        set {
            super.accessibilityElements = newValue
        }
    }
}

#endif

处理 Mac Catalyst 的拖拽无障碍

场景:让拖拽操作支持 VoiceOver 的”拖拽”手势。

class DraggableItemView: UIView {
    
    override var isAccessibilityElement: Bool {
        get { return true }
        set { super.isAccessibilityElement = newValue }
    }
    
    override var accessibilityTraits: UIAccessibilityTraits {
        get { return [.button, .allowsDirectInteraction] }
        set { super.accessibilityTraits = newValue }
    }
    
    override var accessibilityLabel: String? {
        get { return "可拖拽项目:\(itemTitle)" }
        set { super.accessibilityLabel = newValue }
    }
    
    // 提供 VoiceOver 的自定义操作
    override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
        get {
            return [
                UIAccessibilityCustomAction(name: "拖拽到收藏") { _ in
                    self.dragToFavorites()
                    return true
                },
                UIAccessibilityCustomAction(name: "拖拽到回收站") { _ in
                    self.dragToTrash()
                    return true
                }
            ]
        }
        set { super.accessibilityCustomActions = newValue }
    }
    
    private var itemTitle: String = "默认项目"
    
    private func dragToFavorites() { /* 拖拽到收藏夹 */ }
    private func dragToTrash() { /* 拖拽到回收站 */ }
}

适配高对比度模式

场景:在 Mac Catalyst 中检测和适配高对比度模式。

class HighContrastAwareView: UIView {
    
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        
        // 检测高对比度模式变化
        if traitCollection.accessibilityContrast != previousTraitCollection?.accessibilityContrast {
            updateAppearanceForContrast()
        }
    }
    
    private func updateAppearanceForContrast() {
        switch traitCollection.accessibilityContrast {
        case .high:
            // 高对比度模式:使用更粗的边框和更深的颜色
            layer.borderWidth = 2
            layer.borderColor = UIColor.labelColor.cgColor
            
            // 确保文字和背景之间有足够的对比度
            backgroundColor = .systemBackground
            
        case .normal, .unspecified:
            // 正常模式
            layer.borderWidth = 0.5
            layer.borderColor = UIColor.separator.cgColor
            backgroundColor = .secondarySystemBackground
            
        @unknown default:
            break
        }
    }
}

跨平台共享无障碍配置

场景:使用条件编译在 iOS 和 macOS 之间共享代码。

extension UIView {
    
    func configureAccessibility(label: String,
                                 hint: String? = nil,
                                 traits: UIAccessibilityTraits = .button) {
        isAccessibilityElement = true
        accessibilityLabel = label
        accessibilityHint = hint
        accessibilityTraits = traits
        
        #if targetEnvironment(macCatalyst)
        // Mac 特有的无障碍配置
        // 确保支持键盘焦点
        if traits.contains(.button) {
            // Mac 上按钮需要明确支持焦点
            canBecomeFocused = true
        }
        #endif
    }
}

// 使用示例
class MyButton: UIButton {
    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        configureAccessibility(
            label: "保存文档",
            hint: "双击保存当前文档",
            traits: .button
        )
    }
}

最佳实践

已有项目:如果你有 Mac Catalyst App,用 Mac 的 VoiceOver 测试一遍核心流程。打开 VoiceOver(Command + F5),用 Tab 键和 VO 键导航你的 App。常见问题包括:Tab 键跳过了某些按钮、自定义视图没有焦点环、拖拽操作无法用键盘完成。

新项目:从设计阶段就考虑 Mac 的无障碍需求。确保所有交互元素都有 accessibilityLabel,Tab 导航顺序合理,自定义操作有对应的键盘快捷键。在 CI 中加入 Accessibility Inspector 的自动化检查。

还有什么值得关注

  • Mac 的 Accessibility Inspector 比 iOS 版本更强大,可以检查焦点链、模拟键盘导航、检测对比度问题。
  • macOS 的”缩放”辅助功能会放大鼠标周围的区域,确保你的 UI 在缩放下仍然可读。
  • UIAccessibility.isVoiceOverRunning 可以在运行时检测 VoiceOver 状态,用于优化性能。
WWDC 2020