张赐荣,视障者,信息无障碍专家
深耕Web/PC/移动端可访问性研究与实践工作多年,对跨平台无障碍解决方案拥有深刻的独特理论和丰富的实战经验。
精通视障用户软件交互设计,致力于用专业的能力改善、提升产品可及性体验。

張賜榮

张赐荣的技术博客

博客园 首页 新随笔 联系 订阅 管理

在 VoiceOver 操作转子中添加自定义动作

Custom action:一个少有人知的无障碍 API

UIAccessibilityCustomAction 很早就有了。但很多 iOS 无障碍开发者并不知道这个 API。

该 API 主要用来给一个元素挂上无障碍自定动作,这些动作不会成为独立的 VoiceOver 焦点,而是收纳到“操作”转子项目中,使用者需要的时候,焦点在有操作的元素上,双指旋转到“操作”转子,上下滑动就可以找到这些动作。

就这一个简简单单的 API,却能极大提升 VoiceOver 用户的使用效率。

自绘浮层菜单的无障碍适配困境

比如一个聊天消息列表就是一个特别典型的场景。

一条消息,除了“查看详情”这个主要交互之外,通常还有“复制”、“转发”、“删除”、“引用回复”等一堆附加操作。

健视用户的交互通常是长按消息 → 弹出一个无障碍适配不佳的自绘功能表浮层 → 点击“复制”。

这个流程在视觉上很顺畅,长按的位置就是功能表出现的位置,视觉焦点不需要移动。

但 VoiceOver 用户使用同样方法,体验却截然不同。

首先说长按这个手势本身。VoiceOver 使用者的长按方式和明眼人不太一样,视障使用者需要先按两下按住不放,等系统识别为长按。这个过程中,时长、压力、手指稳定都有要求。按短了变成“聚焦”,按长了可能触发双击激活,稍微抖一下,就会出错。

然後是功能表浮层的问题。长按触发弹出的功能表是一系列新的 UI 元素,对於障碍使用者来说,容易打破操作预期,功能表浮层出现时,VoiceOver 需要把焦点从原聚焦控制项瞬间移进浮层。这个改变过程,在很多自定非标功能表中,会出现失焦问题,用户听到的朗读突然中断,要麽焦点没有进入浮层,或者焦点没有停留在第一个可操作功能表项目上,使用者需要自己摸索当前在哪,更甚整个浮层存在焦点穿透问题,也就是使用者能流览到功能表浮层以外的当前不可操作的内容,复杂的焦点管理,让自定功能表适配无障碍困难重重。

还有一个问题,VoiceOver 使用者流览萤幕时,手指在元素上滑动,系统逐个朗读内容。长按功能表是一个“中断”行为,用户在流览过程中突然被要求处理一个浮层,处理完了还要手动把焦点移回之前的位置继续流览。这个中断成本比视觉使用者高得多,因为视觉使用者可以看到功能表出现,依然知道“我原来在看哪一条消息”。

面对自定功能表在无障碍下的体验问题,很多开发者的第一反应就是,“不用长按了,把操作作为按钮直接平铺到焦点序列中。”

於是每条消息後面跟了三个按钮:“复制”“转发”“删除”。VoiceOver 用户单指左右滑动的时候,会依次经过:

消息内容 → 复制按钮 → 转发按钮 → 删除按钮 → 下一条消息内容 → 复制按钮 → 转发按钮 → 删除按钮 …

这么做确实很简单,但却是灾难级的体验。

十条消息,光这些操作按钮就有三十个焦点。使用者想从第一条消息滑到最後一条,中间要经过三十个“复制”“转发”“删除”。而且这些按钮朗读起来完全一样,一不小心滑过头,又得倒回去重新找。

转子操作的自定动作则用一个非常优雅的方法,解决了这个问题,在不增加 VoiceOver 焦点数量的前提下,给用户提供多个操作入口。

自定动作

UIAccessibilityCustomAction 的设计是,不把关联动作,如“复制”“转发”“删除”变成独立焦点,而是把它们挂在消息内容这个元素上,作为“操作转子”里的可选操作。

使用者的实际操作路径是这样的:

  1. 单指滑到消息内容 → 听到“张三:明天下午三点开会”
  2. 双指旋转 VoiceOver 转子,切到“操作”
  3. 单指上下滑动 → 听到“复制”“转发”“删除”
  4. 听到“复制”,按两下执行

对比长按功能表的路径:

  1. 滑到消息内容
  2. 单指双击并长按,等待系统识别手势
  3. 菜单浮层出现,焦点跳入浮层
  4. 在浮层里滑动找“复制”
  5. 找到後按两下执行

自定动作省掉了“等待浮层出现”和“焦点在浮层里盲找”两步。而且整个操作过程中,焦点始终在消息本身,没有被打断,用户知道自己在操作什吗。

UIKit 实现

UIAccessibilityCustomAction 的构造非常轻量。

let copyAction = UIAccessibilityCustomAction(
    name: "复制"
) { _ in
    self.copyMessage()
    return true
}

let forwardAction = UIAccessibilityCustomAction(
    name: "转发"
) { _ in
    self.forwardMessage()
    return true
}

let deleteAction = UIAccessibilityCustomAction(
    name: "删除"
) { _ in
    self.deleteMessage()
    return true
}

cell.accessibilityCustomActions = [copyAction, forwardAction, deleteAction]

闭包里的 return true 表示“这个操作我处理了”。如果返回 false,则表示没有处理动作。

多个动作按顺序在操作转子中展示。VoiceOver 使用者上下滑动时依序流览。

还有一个相关的方法是 accessibilityActivate。它重写的是 VoiceOver 使用者按两下元素时的默认动作。

override func accessibilityActivate() -> Bool {
    // 按两下这条消息时,预设执行查看详情
    self.openMessageDetail()
    return true
}

accessibilityCustomActions 是追加到系统内置动作之后的,不是替换,系统原有的Actions依然会存在。

SwiftUI 实现

SwiftUI 的修饰器写法更简洁。

Text(message.content)
    .accessibilityAction(named: "复制") {
        copyMessage(message)
    }

多个动作:

MessageView(message)
    .accessibilityActions {
        Button("复制") { copyMessage(message) }
        Button("转发") { forwardMessage(message) }
        Button("删除") { deleteMessage(message) }
    }

每个 Button 的标题会成为操作转子里的动作名称,action 闭包负责执行。

SwiftUI 还提供了一些系统预定义的动作类型,用 AccessibilityActionKind 表示:

.accessibilityAction(.delete) {
    deleteMessage()
}

.delete.escape 这些 API 使用系统定义的 accessibility action semantics 标准化动作语义,其名称和呈现由系统/辅助技术处理,通常不需要自己定义对应的本地化动作名称。

iOS 新系统的功能

当自定动作太多的时候,操作转子本身也会变成一个长列表。iOS 18 引入了Category来解决这个问题。

UIKit 里给 action 加一个 category 属性:

let action = UIAccessibilityCustomAction(name: "重命名") { ... }
action.category = UIAccessibilityCustomAction.editCategory

SwiftUI 里用专门的修饰器:

EditorView()
    .accessibilityActions(category: .edit) {
        Button("重命名") { ... }
        Button("删除") { ... }
    }

editCategory 为VoiceOver提供信息,开发者可将具有编辑语义的动作组织归类到编辑转子中。

总之,主要、高频、需要快速发现的操作,应考虑作为独立的 accessibility element;次要、低频、与当前元素紧密相关的操作,则非常适合使用 custom actions。


参考

有关如何在 Android 中实现类似功能,可参阅 《Android TalkBack 无障碍优化: 为控件自定义转子导航动作》

了解更多,请参阅:

  1. Apple Developer Documentation. UIAccessibilityCustomAction. https://developer.apple.com/documentation/uikit/uiaccessibilitycustomaction

  2. Apple WWDC19. Making Apps More Accessible With Custom Actions (Session 250). https://developer.apple.com/videos/play/wwdc2019/250/

  3. Apple Developer Documentation. accessibilityAction(named:_😃 - SwiftUI. https://developer.apple.com/documentation/swiftui/view/accessibilityaction(named:_😃

  4. Apple Developer Documentation. accessibilityActions(_😃 - SwiftUI. https://developer.apple.com/documentation/swiftui/view/accessibilityactions(_😃

  5. Apple Developer Documentation. AccessibilityActionKind. https://developer.apple.com/documentation/swiftui/accessibilityactionkind

  6. Apple Developer Documentation. editCategory. https://developer.apple.com/documentation/uikit/uiaccessibilitycustomaction/editcategory

  7. Apple Developer Documentation. accessibilityActions(category:_😃 - SwiftUI. https://developer.apple.com/documentation/swiftui/view/accessibilityactions(category:_😃

  8. Apple HIG. Accessibility - VoiceOver. https://developer.apple.com/design/human-interface-guidelines/accessibility#VoiceOver


作者

张赐荣,视障者,信息无障碍软件优化解决方案资深研发工程师。

深耕Web/PC/移动端可访问性工作多年,对跨平台无障碍解决方案拥有独特的理论研究和丰富的实战经验。

精通视障软件交互设计,致力于改善、提升产品可及性体验。

posted on 2026-08-29 12:58  张赐荣  阅读(11)  评论(0)    收藏  举报

感谢您访问张赐荣的技术分享博客!
博客地址:https://cnblogs.com/netlog/
知乎主页:https://www.zhihu.com/people/tzujung-chang
个人网站:https://prc.cx/