IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    ios 为视障用户支持 VoiceOver

    R0uter发表于 2016-12-22 01:39:06
    love 0

    去落格博客阅读完整排版的ios 为视障用户支持 VoiceOver

    其实很少用户知道,ios系统其实有一套完整的轻松访问机制,很多盲人或者说视障用户都喜欢使用iphone。

    所以说,作为一名开发者,我觉得不论是从产品销售面还是作为责任,都应该做好完善的轻松访问支持。

    不过好在,得益于苹果严格的开发规范,所以一般只要你的app已经通过审核能够上架,那么基本上 VoiceOver 就已经能够很好的识别你 app 中的大部分内容了,比较通用的,比如 tabView,比如 navigationController,这些通用的框架都已经自动支持了的。现在,我们就一起来看看,如何让你的 app 利用好 VoiceOver 的所有高级功能,这些功能能够让你在一定程度上控制 VoiceOver,让它更好地兼容你的app,让用户体验更佳完美。

    主要目标

    VoiceOver 除了能够读出屏幕内容外,其实还可以做更多的内容,比如与界面交互,比如内置的魔法轻拍,魔法返回,要让我们自定义的内容支持它,就需要自己去重写对应的方法,还要让我们自己定制的按钮,按照希望的方式来运作——毕竟使用 VoiceOver 的用户是靠听的,所以有时候我们还需要为按钮等加上更多解释,甚至,在一些动作之后,还要让 VoiceOver 读出特定的内容来告知用户都发生了什么,把这些都规范了,那么视障用户也能轻松地使用你开发的 app 了。

    VoiceOver 运行状态

    要对 VoiceOver 进行支持,那么得先要能发现它的状态——虽然其实大部分情况下你不需要这么做,因为你在对 VoiceOver 做多配置,如果 VoiceOver 不开启的话,就不会有任何影响,但如果你希望检测一下的话,在 UIKit 里有对应的全局函数:

    var isVoiceOverOn:Bool {return UIAccessibilityIsVoiceOverRunning()}

    当然,你也可以不用像我这样在封装一层,我是为了自己整体代码看起来更一致罢了。

    设定可读

    对于一个自定义的控件,你需要手动地来为它指定是否要作为一个 VoiceOver 控件:

    someButton.isAccessibilityElement = true

    控件角色

    ios 为控件提供了一些常见的角色可以配置,比如按钮,比如音量键,为你的控件设定合适恰当的控件角色,这有助于 VoiceOver 为你的控件提供对应的额外功能。

    举例来说,对于键盘按钮,就要给按钮去掉按钮角色而使用键盘按钮角色,只有这样,用户才能实现用手指在键盘上滑动,然后找到需要的按钮时,松开手指立即执行按下的操作。

    accessibilityTraits = UIAccessibilityTraitKeyboardKey

    当然,其实这些角色可以是多个,我们通过最后的声明可以看到这个角色类型其实就是一个大的整数:

    public typealias UIAccessibilityTraits = UInt64
     所以我们可以这样:

    accessibilityTraits = UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitButton

    这里我顺便为大家翻译一下声明里的翻译,具体的特性需要大家自己去尝试。

    // 不使用角色,在某些情况下很有用,比如你想自己处理它的时候,落格输入法的候选字,既想让它有按钮的动作,又不想让它读出来“按钮”这个烦人的提示,那么就用这个了。
    public var UIAccessibilityTraitNone: UIAccessibilityTraits
    
    // 如果是系统拖的按钮,那么默认肯定就是这个。如果你给按钮的 VoiceOver title 设定的名字是xx按钮,那么VO就不会给你追加“按钮”俩字。
    public var UIAccessibilityTraitButton: UIAccessibilityTraits
    
    // 控件被用于链接的时候使用
    public var UIAccessibilityTraitLink: UIAccessibilityTraits
    
    // 控件作为内容的标题的时候使用,比如说导航栏的标题。
    @available(iOS 6.0, *)
    public var UIAccessibilityTraitHeader: UIAccessibilityTraits
    
    // 搜索栏
    public var UIAccessibilityTraitSearchField: UIAccessibilityTraits
    
    // 图片,可以和按钮、链接之类的混合使用。
    public var UIAccessibilityTraitImage: UIAccessibilityTraits
    
    /*
     被选中的控件。
     比如说tableview里被选中的那一行。
     */
    public var UIAccessibilityTraitSelected: UIAccessibilityTraits
    
    // 当控件自身可以播放声音的时候
    public var UIAccessibilityTraitPlaysSound: UIAccessibilityTraits
    
    // 当控件是键盘按钮的时候
    public var UIAccessibilityTraitKeyboardKey: UIAccessibilityTraits
    
    // 不能改变的静态文本
    public var UIAccessibilityTraitStaticText: UIAccessibilityTraits
    
    /*
    当作为提供当前情况快速总结的时候。
    比如说,一个天气应用,第一次打开时当天的天气情况就可以标记为这个。
     */
    public var UIAccessibilityTraitSummaryElement: UIAccessibilityTraits
    
    // 控件不可用的时候
    public var UIAccessibilityTraitNotEnabled: UIAccessibilityTraits
    
    /*
    当控件要频繁地更新其label或者值的时候
    比如说秒表。
     */
    public var UIAccessibilityTraitUpdatesFrequently: UIAccessibilityTraits
    
    /*
    当控件启动不应该被VO干扰的媒体会话的时候(比如说播放电影,录音)
     */
    @available(iOS 4.0, *)
    public var UIAccessibilityTraitStartsMediaSession: UIAccessibilityTraits
    
    /*
    当控件可以被“调节”的时候,比如说滑动条。
    控件也要实现  accessibilityIncrement 和 accessibilityDecrement.
     */
    @available(iOS 4.0, *)
    public var UIAccessibilityTraitAdjustable: UIAccessibilityTraits
    
    // 当控件需要直接与用户交互的时候使用,比如说钢琴键盘。
    @available(iOS 5.0, *)
    public var UIAccessibilityTraitAllowsDirectInteraction: UIAccessibilityTraits
    
    /*
    通知VO完成阅读时要滚动到下一个页面。VO会调用 accessibilityScroll: with UIAccessibilityScrollDirectionNext 并且在检测到内容不变时停止。
     */
    @available(iOS 5.0, *)
    public var UIAccessibilityTraitCausesPageTurn: UIAccessibilityTraits
    
    /*
     Used when a view or accessibility container represents an ordered list of tabs.
     The object with this trait should return NO for isAccessibilityElement.
     */
    @available(iOS 10.0, *)
    public var UIAccessibilityTraitTabBar: UIAccessibilityTraits

    魔法轻拍

    这是一个很常用的手势,就是双指在屏幕上双击。比如说在落格输入法中,它用来快速返回第一个候选(当你往后翻了很久的时候)。如果你想监听这个动作,就在对应的 Controller 里重写 

    accessibilityPerformMagicTap
     :

    override func accessibilityPerformMagicTap() -> Bool {
        ...
     }

    返回

    true
     来告诉系统,VoiceOver 接收了操作,返回
    false
     来说明跳过,将内容发送给其他接受者。

    魔法返回

    魔法返回的手势有点难了,但稍微练习一下应该也不是什么难事,总之,在能够返回的地方比如 navigationController 中双指左右来回一下就是执行了一次魔法返回。还是用落格输入法举例子,那么就是在键盘上执行魔法返回来收起键盘:

    override func accessibilityPerformEscape() -> Bool {
            dismissKeyboard()
            return true
        }

    手动发出提示

    那么,怎么才能主动给用户发出内容提示呢?比如“进入表情键盘”?那么你可以这样:

    UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, "进入表情键盘");

    那么VO就会在你调用它发送通知后读出你的内容,所以,记得要做本地化。

    同时,值得一提的是,如果你同时发送多条通知,那么后面的通知会把前边的通知给覆盖掉!比如这样:

    UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, "进入表情键盘");
    UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, "进入符号键盘");
    UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, "进入拼音键盘");

    那么你得到的很可能是“进入表情键盘”或“进入拼音键盘”,其它的两个就丢失了。

    还有比如说在落格输入法之前的版本中,一直存在一个bug,那就是VO里无法读出删除的字。这对于视障用户来说是一件很重要的问题,因为如果你不读出来的话他们就不能确定自己删除了啥,那可真是糟糕透了。而且比这还糟糕的是,我自己本地测试是完全正常的!

    所以说,一定要注意这个问题,发送通知不需要一定要在主线程(如果你和我一样常用并发的话),但一定要注意间隔,经过分析,最终我才明白,当你在键盘上点击了删除,那么作为一款第三方键盘,我必须调用内置的api来删除,可是这个删除会触发UI改变(因为文字变了对吧?)VoiceOver就会去傻傻地报告光标位置,显然,一般输入错了肯定是最后一个字居多,那么自然bug就成了读“文本底部”。

    这时候,只需要比系统的通知慢就好了,(系统通知比你发的要慢一点,所以它老是覆盖我的通知即使我把它放在了删除调用之后),所以,我这样处理了:

    DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
                            UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, String(word.characters.last!));
                        })

    总之,慢这么0.1秒对用户来说他们发现不了什么区别,但对于系统来说,那就是有了足够的时间让VO在收到系统的通知后,还来不及读出就被你的通知给覆盖掉了。

    手动改变焦点

    当用户点了键盘,或者说做了一些操作之后,我们需要让VO自动将焦点移动到上边,典型的情况就是你在点击了键盘按钮后,候选栏会更新新的候选词,这个时候讲焦点移动到第一个候选就会很好用。因为这样的话,用户就可以双击屏幕直接选中它了。所以,我这样做:

    UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self);

    第二个实际参数就是要获得焦点的控件引用,这样收到通知后,VO会立即将焦点移动到这个空间上并读出它的内容。

    总结

    基本上来说,对于 VoiceOver 的测试,你是无法在 xcode 中完成的,它所附带的工具仅仅能够支持最简单的检查,想要完整地测试你的VO支持,你应该把app安装到你的 iphone 上,然后打开VoiceOver,闭上眼睛去亲自使用一番,如果不擅长 VoiceOver 的你都能顺利地使用你的app,那应该就没什么大问题了。

    以上这些,就是我把落格输入法做成 ios 平台 VoiceOver 支持最好的中文输入法的人生经验了。😉

    ios 为视障用户支持 VoiceOver,首发于落格博客。

    其他推荐:
    1. 译:我如何在 Swift 声明 闭包?
    2. Swift 里的 单件模式
    3. 初始化器
    4. Swift 中的正则表达式
    5. Swift 里的 Stack 实现



沪ICP备19023445号-2号
友情链接