带你认识——iOS Accessibility

1. Accessibility是什么

在Apple的定义中,我们可以理解为:无障碍使用。所谓“无障碍使用”,是对于“有障碍人士来说”,比方说让一位有眼疾的用户可以“无障碍”使用iPhone上的APP。
iOS的设备天生就具备这一特性,打开“设置 -> 通用 -> 辅助功能”,就可以看见Apple提供的一系列的Accessibility功能,这里可以说几个比较常见的:

  1. VoiceOver:VoiceOver是苹果创新的屏幕阅读技术,用户通过触摸屏幕,随后APP返回语音信息,从而引导用户操作
  2. 缩放(Zoom):顾名思义,缩放屏幕上的元素
  3. 显示调节——反转颜色(White on Black):反转显示屏上的颜色

2. VoiceOver和Accessibility

本篇文章主要讨论的是UIAccessibility的API在VoiceOver上的运用。正如上文所讲到的,VoiceOver是苹果创新的屏幕阅读技术,用户通过触摸屏幕得到语音信息,从而做出正确并且符合预期的操作。将自己想象为一位有眼疾的人士,无法通过自己的双眼来获取到屏幕上的信息,只能通过语音提示来完成自己想要的操作,那么这就需要语音信息必须要准确、完整、简洁地反映出当前屏幕上所展示出的UI信息。为此,Apple为iOS设计出了一套API,通过它,开发者们就可以让自己的APP实现VoiceOver的功能。
这一套API主要包括以下几个部分:

  1. UIAccessibility(Informal Protocol):实现UIAccessibility协议的对象报告其可访问性状态(即是否可访问),并提供有关其自身的描述性信息。 默认情况下,标准的UIKit控件和视图实现了UIAccessibility协议
  2. UIAccessibilityContainer(Informal Protocol):该协议一般用于UIView的子类,可以让它所包含的一些子UI作为单独的元素被访问到。当一个视图所包含的子对象并不是UIView的子类,而又需要被访问到的时候,这个协议就非常有用。
  3. UIAccessibilityElement(Class):这个类默认实现了UIAccessibility协议,可以为一个不能被自动访问到的对象(例如非UIView的子类)创建一个该类的实例,从而让它可以被访问到。

3. UIAccessibility

UIAccessibility是iOS的Accessibility最核心的一套API,它实际上是一个非正式协议,里面提供了一系列的方法来提供UI的辅助信息。举个例子,在开启了VoiceOver的情况下,在用户触摸到某个UI的时候,APP就会将这些方法提供的关于此UI的辅助信息转化成语音念出来。
这里有必要说一下什么是非正式协议:

An informal protocol is a category on NSObject, which implicitly makes almost all objects adopters of the protocol. (A category is a language feature that enables you to add methods to a class without subclassing it.) Implementation of the methods in an informal protocol is optional. Before invoking a method, the calling object checks to see whether the target object implements it. Until optional protocol methods were introduced in Objective-C 2.0, informal protocols were essential to the way Foundation and AppKit classes implemented delegation.

非正式协议实际上是基于NSObject的一个分类,它的子类们都隐式地遵守了该协议。在非正式协议中声明的方法,可以选择实现与否。这就很清楚了,在还没有引入“optional protocol methods”之前,非正式协议就是一种替代方案。

3.1 UIAccessibility API

回到UIAccessibility,它里面有几个比较重要并且常用的API:

  1. accessibilityLabel:一个简短的本地化的词或短语,简洁地描述了控件或视图,但不能识别元素的类型。 例如“添加”或“播放”
  2. accessibilityHint:一个简短的本地化短语,用于描述作用于元素上的操作所得到的结果。 例如“添加标题”或“查看购物车”
  3. accessibilityTraits:一个或多个单独特征的组合,每个特征描述元素的某一个方面,包括状态、行为或用法。这些都定义成了UIAccessibilityTrait的某一个常量。
  4. accessibilityFrame:屏幕中某元素的坐标和大小
  5. accessibilityValue:某UI元素的当前值,并且这个值无法由label表示出来。例如,某个slider的标签可能是“速度”,但其当前值可能为“50%”
    贴一张官方文档的图,便于理解:
    [image:DD012E17-8603-4975-B796-DDEC36F39C70-30478-00017DF4F61F5B6F/accessibility.png]

3.2 UIAccessibility的正确使用

标准的UIKit controls和views都是自动无障碍的,所以只需要确保它们默认提供的无障碍信息是否准确。
如果写了一个自定义的view为用户提供信息,或者一个可以自定义的可以交互的控件,那么你就需要自己实现它的无障碍配置。这就是我们常见的情况了,这种情况又可以分为以下两种:

  1. an individual view:这个view里面不包含任何子元素
  2. a container view:这个view里面包含其他子元素,并且这些子元素都需要做无障碍配置

3.2.1 Make Custom Individual Views Accessible

// 这个view为某个自定义视图的实例
// 方式一:直接给属性赋值
view.isAccessibilityElement = YES
// 方式二:实现UIAccessibilityProtocol协议中的方法
- (BOOL)isAccessibilityElement {
    return YES;
}

3.2.2 Make the Contents of Custom Container Views Accessible

这种情况是:你有一个视图容器,里面包含了很多的其他元素,而这些元素提供了某些信息或者可以进行交互,也就是说这些子元素需要配置成accessible,而你的视图容器不需要配置成accessible。
在此种情况下,你的视图容器就需要实现UIAccessibilityContainer协议,该协议中的方法会将这些需要配置成accessible的子元素放在一个数组中。

// 这里我们假定这些子元素都没有默认实现UIAccessibility协议,这时就需要用到UIAccessibilityElement
@implementation ContainerView
- (NSArray *)accessibleElements {
   if ( _accessibleElements != nil ) {
      return _accessibleElements;
   }
   _accessibleElements = [[NSMutableArray alloc] init];
   // 每一个子元素都是一个UIAccessibilityElement,将他们全都添加到_accessibleElements中
   UIAccessibilityElement *element1 = [[[UIAccessibilityElement alloc] initWithAccessibilityContainer:self] autorelease];
   [_accessibleElements addObject:element1];
   UIAccessibilityElement *element2 = [[[UIAccessibilityElement alloc] initWithAccessibilityContainer:self] autorelease];
   [_accessibleElements addObject:element2];
   return _accessibleElements;
}
// 这个ContainerView是不支持Accessibility的
- (BOOL)isAccessibilityElement {
   return NO;
}
// UIAccessibilityContainer协议方法
- (NSInteger)accessibilityElementCount {
   return [[self accessibleElements] count];
}
- (id)accessibilityElementAtIndex:(NSInteger)index {
   return [[self accessibleElements] objectAtIndex:index];
}
- (NSInteger)indexOfAccessibilityElement:(id)element {
   return [[self accessibleElements] indexOfObject:element];
}
@end

3.3 确保Accessibility各属性的准确可靠

在对Accessibility进行配置开发的时候,其实最重要的就是保证你所传达的信息的准确可靠,并在此前提下尽量保持信息的简洁。如何满足这个要求?就是深入理解苹果公司对UIAccessibility中API的定义,最重要的就是:accessibilityLabel,accessibilityHint,accessibilityTraits,accessibilityValue。这一部分在上文已经简单解释过了,就不赘述了。这里只列出UIAccessibilityTraits的各种特征值:(正确使用尤为重要)

  1. Button
  2. Link
  3. Search Field
  4. Keyboard Key
  5. Static Text
  6. Image
  7. Plays Sound
  8. Selected
  9. Summary Element
  10. Updates Frequently
  11. Not Enabled
  12. None
    那么对accessibilityLabel,accessibilityHint,accessibilityTraits正确设置完之后会有什么效果呢?在Voice Over情况下,它们都会以语音的形式输出。
  13. label和hint返回的字符串,会以语音的形式念出来,返回什么,念什么
  14. 配置的traits的值,也会以语音的形式念出。比如:UIAccessibilityTraitSelected | UIAccessibilityTraitImage | UIAccessibilityTraitButton会变成“已选定,图片,按钮”

4. 一些高级用法

4.1 动态变化

// 这个方法的用处:当你的界面中某些元素是动态的,比方说hidden之类的,它们发生变化时,可以用该方法发送通知
// 此时屏幕上的accessible元素会变成element这个元素,即接受通知的元素
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, element);

4.2 UIAccessibilityAction

这个非正式协议里面提供了一些函数,用来支持一些比较特殊的需要在Accessibility情况下完成的行为。
这里列出比较常用的三个函数:(Magic Tap单独提出来)

// 默认的双击之后会触发的函数
- (BOOL)accessibilityActivate;
// 增加(减少)此元素的accessibilityValue
- (void)accessibilityIncrement;
- (void)accessibilityDecrement;

Magic Tap

- (BOOL)accessibilityPerformMagicTap;
在某些情况下,我们的app会有一个最核心、重要的功能,比方说音乐播放器最重要的功能就是播放音乐,手机phone最重要的功能就是接听电话,clock最重要的功能可能就是关闭闹钟了……那么在Voice Over的情况下,Magic Tap为我们提供了直接的手势来触发该功能,即“两根手指轻点屏幕两次”。也就是说,你在app的任何位置,只要做出了Magic Tap的手势,就会触发该函数。
需要注意的是:Magic Tap在你的App中应该只在AppDelegate文件中实现一次,这样才满足Magic Tap的设定和意义。(我尝试了在其他文件中也实现,但是并没有被触发)

4.3 触发事件

在Voice Over的情况下,选中到该元素后,通过上划或者下划来选择需要触发的事件(这个上划下划的手势我也是摸索了好久才发现)

UIAccessibilityCustomAction *helloAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"Say hello", @"Accessibility action to say hello") target:self selector:@selector(sayHello)];
UIAccessibilityCustomAction *goodbyeAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"Say goodbye", @"Accessibility action to say goodbye") target:self selector:@selector(sayGoodbye)];
element.accessibilityCustomActions = @[helloAction, goodbyeAction];

5. 总结/经验

  1. iOS模拟器的Accessibility Inspector可以用来调试部分Accessibility功能
  2. 动画,特别是交互动画,和手势触摸相关的,这种没有什么好的实现方法。想来也应该,动画优化的是视觉体验,在但是使用Voice Over都是视力有所障碍的人群,没必要实现这一部分功能
  3. Accessibility旨在为有障碍人群提供准确简洁的信息,以方便他们使用App。那么如何提供信息,信息是聚合还是分散,这在不同情况下需要做出不同的选择,需要配置为accessible的元素也需要视情况而定
  4. Accessibility的配置并不复杂,且跟UI高度相关,建议在写UI的同时考虑一下Accessibility的问题,如果需要配置为accessible,那么可以顺手完成

参考文档:Accessibility Programming Guide for iOS

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 介绍 1.开启voiceOver 设置->通用->辅助功能->VoiceOver 在voiceOver模式中 a....
    linatan阅读 6,034评论 0 8
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,587评论 18 139
  • 本文为大地瓜原创,欢迎知识共享,转载请注明出处。虽然你不注明出处我也没什么精力和你计较。作者微信号:christg...
    大地瓜123阅读 312评论 0 0
  • 一声呼喊,我心荡漾 初衷总是好的,最开始我想大学社团会带给我什么?可以是技能,可以是机遇,可以是挑战。而此刻...
    云凡夫子阅读 215评论 0 0
  • 他为戏子,她是看官。 他尽情表演,她却为他流泪 他曾拥有戏台最热烈的掌声 却因她一句卸下了伪装 一生只为一人的捧场...
    说与鬼听阅读 498评论 2 15