1. Accessibility是什么
在Apple的定义中,我们可以理解为:无障碍使用。所谓“无障碍使用”,是对于“有障碍人士来说”,比方说让一位有眼疾的用户可以“无障碍”使用iPhone上的APP。
iOS的设备天生就具备这一特性,打开“设置 -> 通用 -> 辅助功能”,就可以看见Apple提供的一系列的Accessibility功能,这里可以说几个比较常见的:
- VoiceOver:VoiceOver是苹果创新的屏幕阅读技术,用户通过触摸屏幕,随后APP返回语音信息,从而引导用户操作
- 缩放(Zoom):顾名思义,缩放屏幕上的元素
- 显示调节——反转颜色(White on Black):反转显示屏上的颜色
2. VoiceOver和Accessibility
本篇文章主要讨论的是UIAccessibility的API在VoiceOver上的运用。正如上文所讲到的,VoiceOver是苹果创新的屏幕阅读技术,用户通过触摸屏幕得到语音信息,从而做出正确并且符合预期的操作。将自己想象为一位有眼疾的人士,无法通过自己的双眼来获取到屏幕上的信息,只能通过语音提示来完成自己想要的操作,那么这就需要语音信息必须要准确、完整、简洁地反映出当前屏幕上所展示出的UI信息。为此,Apple为iOS设计出了一套API,通过它,开发者们就可以让自己的APP实现VoiceOver的功能。
这一套API主要包括以下几个部分:
- UIAccessibility(Informal Protocol):实现UIAccessibility协议的对象报告其可访问性状态(即是否可访问),并提供有关其自身的描述性信息。 默认情况下,标准的UIKit控件和视图实现了UIAccessibility协议
- UIAccessibilityContainer(Informal Protocol):该协议一般用于UIView的子类,可以让它所包含的一些子UI作为单独的元素被访问到。当一个视图所包含的子对象并不是UIView的子类,而又需要被访问到的时候,这个协议就非常有用。
- 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:
- accessibilityLabel:一个简短的本地化的词或短语,简洁地描述了控件或视图,但不能识别元素的类型。 例如“添加”或“播放”
- accessibilityHint:一个简短的本地化短语,用于描述作用于元素上的操作所得到的结果。 例如“添加标题”或“查看购物车”
- accessibilityTraits:一个或多个单独特征的组合,每个特征描述元素的某一个方面,包括状态、行为或用法。这些都定义成了UIAccessibilityTrait的某一个常量。
- accessibilityFrame:屏幕中某元素的坐标和大小
- accessibilityValue:某UI元素的当前值,并且这个值无法由label表示出来。例如,某个slider的标签可能是“速度”,但其当前值可能为“50%”
贴一张官方文档的图,便于理解:
[image:DD012E17-8603-4975-B796-DDEC36F39C70-30478-00017DF4F61F5B6F/accessibility.png]
3.2 UIAccessibility的正确使用
标准的UIKit controls和views都是自动无障碍的,所以只需要确保它们默认提供的无障碍信息是否准确。
如果写了一个自定义的view为用户提供信息,或者一个可以自定义的可以交互的控件,那么你就需要自己实现它的无障碍配置。这就是我们常见的情况了,这种情况又可以分为以下两种:
- an individual view:这个view里面不包含任何子元素
- 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的各种特征值:(正确使用尤为重要)
- Button
- Link
- Search Field
- Keyboard Key
- Static Text
- Image
- Plays Sound
- Selected
- Summary Element
- Updates Frequently
- Not Enabled
- None
那么对accessibilityLabel,accessibilityHint,accessibilityTraits正确设置完之后会有什么效果呢?在Voice Over情况下,它们都会以语音的形式输出。 - label和hint返回的字符串,会以语音的形式念出来,返回什么,念什么
- 配置的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. 总结/经验
- iOS模拟器的Accessibility Inspector可以用来调试部分Accessibility功能
- 动画,特别是交互动画,和手势触摸相关的,这种没有什么好的实现方法。想来也应该,动画优化的是视觉体验,在但是使用Voice Over都是视力有所障碍的人群,没必要实现这一部分功能
- Accessibility旨在为有障碍人群提供准确简洁的信息,以方便他们使用App。那么如何提供信息,信息是聚合还是分散,这在不同情况下需要做出不同的选择,需要配置为accessible的元素也需要视情况而定
- Accessibility的配置并不复杂,且跟UI高度相关,建议在写UI的同时考虑一下Accessibility的问题,如果需要配置为accessible,那么可以顺手完成