Voice Over
是苹果出的为方便视力障碍人士使用手机的功能,打开后就可以把屏幕上的内容用语音读出来。通常开发的 app 很少考虑到这个功能,也没有做适配。但是 UILabel
和 UIButton
本身是支持 Voice Over
的,不用做适配都可以自动读出来。但是自定义 View 这样的还是需要做一下适配会体验比较好一点。
一、适配
适配 VoiceOver
最重要的几个方法就是
@property(nonatomic) BOOL isAccessibilityElement;
@property(nonatomic, copy) NSString *accessibilityLabel;
@property(nonatomic, copy) NSString *accessibilityHint;
@property(nonatomic) UIAccessibilityTraits accessibilityTraits;
@property(nonatomic, copy) NSString *accessibilityValue;
accessibilityLabel(标签)
一个简单的词或短语,它简洁明了地描述控件或者视图,但是不能识别元素类型,UIAccessibilityElement必须要有这个属性。例如“添加”、“播放”。
accessibilityHint(提示)
一个简单的词或短语,描述发生在元素上动作的结果。例如“添加标题”或者“打开购物列表”。
accessibilityValue(值)
不是由标签定义的元素时的当前值。仅当元素的内容是可改变并且不能使用label描述时,一个无障碍元素才需要为其赋值。例如,一个进度条的标签可以是”播放进度”,但是它当前的值是“50%”。
accessibilityTraits
这个element的类型以及状态,就是通过traits来表征这个Element的特质,数据类型是一个枚举类型,可以通过按位或的方式合并多个特性。
VoiceOver
会把这几个属性连接起来,朗读顺序为label→value(可选)→traits→hint。但需要注意的是,当某个View的是AccessibilityElement的时候 ,其subviews都会被屏蔽掉,如果想要都读出来,只能改变他们的层次结构,并都设置isAccessibilityElement为YES。这个特性还是挺有用的,比如一个View中有多个Label,那么每一个下面的Label单独访问可能意义不大,那么就可以将这个View设置成可以访问的,然后将其accessibilityLabel设置为所有子Label的 accessibilityLabel的合并值。
这几个属性都是可以赋值也可以重载。不过推荐重载,这样看起来更清晰。 而且对于accessibilityLabel
的重载也有下面的好处:
1、这个是在 Voice Over
聚焦到控件的时候才去调用,那么如果重载的话,就是类似于懒加载这种了,用到的时候才去初始化。
2、对于自定义 View,包括 cell,会有很多状态,显示的内容都是根据状态来的,那么这时候重载的话,就可以把这部分代码写在一起,和 正常的代码区分开。
二、Texture 的适配
关于 Texture
的自定义 view,可以指定 isAccessibilityContainer == YES
,指定这个属性为 YES
之后,Texture
可以自动把这个 view 下面支持 Voice Over
的子控件给组合起来,形成一个整体,并且子控件有 button 的时候,实现了 一个手指上下轻扫 响应自定义事件,分成方便。
但是有个问题就是 Texture
本身没有处理 控件的 accessibilityElementsHidden
属性。而且也没有判断子控件的状态是显示还是隐藏,只是 遍历了子控件,能读的就给读出来。所以 Texture
的适配就是 判断隐藏的子控件,不能用隐藏方法,只能是直接不添加,不调用addSubnode
。
三、播放自定义语音,自动读出Toast内容
直接上代码:
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, message);
复制代码
利用这个功能,可以读出 Toast
的内容,目前toast
控件,大部分都是直接 addSubview 到UIWindow
上的,这样实现不会自动聚焦,直到自动消失之后也读不出来。可以用这个方法来读出 TOAST
的 内容,或者其他的提示。
但是有个主意的地方就是如果当前正在播报内容,这个时候直接调用时读不出来的,所以加了个延迟
hud.isAccessibilityElement = YES;
hud.accessibilityLabel = message;
hud.accessibilityTraits = UIAccessibilityTraitStaticText;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, message);
});
复制代码
四、自动聚焦
直接上代码
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, VIEW);
复制代码
五、自定义事件
UIAccessibilityCustomAction
,用这个类来初始化来实现自定义的事件,自定义事件在 单个手指上下轻扫后 出来,如果有多个的话,继续上下轻扫来切换,然后再单个手指 双击来响应该事件(例如 UITableViewCell 的删除事件)。
初始化后,可以绑定到控件的
@property (nullable, nonatomic, strong) NSArray <UIAccessibilityCustomAction *> *accessibilityCustomActions NS_AVAILABLE_IOS(8_0);
复制代码
属性上。这个属性是 NSObject
的扩展。所以 UIKit
的控件应该都支持。
这个属性针对 Cell
的适配非常有用。例如 电商类的 app,一个 cell 也许是一个商品,这时候可以把整个 cell 设置 isAccessibilityElement
,指定为 AccessibilityElement
,成为一个整体,然后组合商品名字,价格属性等 来读出,然后 利用 accessibilityCustomActions
来直接加入购物车。
六、提示框 Alert & Actionsheet
系统的 UIAlert 和 UIActionSheet 当然系统做了自动适配,没有问题。问题在于 自定义的 Alert 和 ActionSheet。
1、如果是 只用 用 addSubview
到 keyWindow
的话,那么 提示框出来之后压根就不会自动聚焦读出,只能手指点上去才会读出,这个对盲人不现实。
2、如果用了自己生成的一个 window,然后把 alert 添加到这个 window 上的话,那么会有自动聚焦,再显示弹出框的时候可以自动读出,但是当手指不小心碰到空白区域后,焦点就会跑到背景上下面正常显示的 view,会读出背景后面的内容。
为了解决这个问题,那么对于自定义的 Alert 都改为了 利用 presentViewController
的方式来实现。可以自动读出,也不会点到空白区域就失去焦点。见这里。