1. Accessibility是什么
在Apple的定義中,我們可以理解為:無障礙使用。所謂“無障礙使用”,是對于“有障礙人士來說”,比方說讓一位有眼疾的用戶可以“無障礙”使用iPhone上的APP。
iOS的設備天生就具備這一特性,打開“設置 -> 通用 -> 輔助功能”,就可以看見Apple提供的一系列的Accessibility功能,這里可以說幾個比較常見的:
- VoiceOver:VoiceOver是蘋果創(chuàng)新的屏幕閱讀技術,用戶通過觸摸屏幕,隨后APP返回語音信息,從而引導用戶操作
- 縮放(Zoom):顧名思義,縮放屏幕上的元素
- 顯示調(diào)節(jié)——反轉(zhuǎn)顏色(White on Black):反轉(zhuǎn)顯示屏上的顏色
2. VoiceOver和Accessibility
本篇文章主要討論的是UIAccessibility的API在VoiceOver上的運用。正如上文所講到的,VoiceOver是蘋果創(chuàng)新的屏幕閱讀技術,用戶通過觸摸屏幕得到語音信息,從而做出正確并且符合預期的操作。將自己想象為一位有眼疾的人士,無法通過自己的雙眼來獲取到屏幕上的信息,只能通過語音提示來完成自己想要的操作,那么這就需要語音信息必須要準確、完整、簡潔地反映出當前屏幕上所展示出的UI信息。為此,Apple為iOS設計出了一套API,通過它,開發(fā)者們就可以讓自己的APP實現(xiàn)VoiceOver的功能。
這一套API主要包括以下幾個部分:
- UIAccessibility(Informal Protocol):實現(xiàn)UIAccessibility協(xié)議的對象報告其可訪問性狀態(tài)(即是否可訪問),并提供有關其自身的描述性信息。 默認情況下,標準的UIKit控件和視圖實現(xiàn)了UIAccessibility協(xié)議
- UIAccessibilityContainer(Informal Protocol):該協(xié)議一般用于UIView的子類,可以讓它所包含的一些子UI作為單獨的元素被訪問到。當一個視圖所包含的子對象并不是UIView的子類,而又需要被訪問到的時候,這個協(xié)議就非常有用。
- UIAccessibilityElement(Class):這個類默認實現(xiàn)了UIAccessibility協(xié)議,可以為一個不能被自動訪問到的對象(例如非UIView的子類)創(chuàng)建一個該類的實例,從而讓它可以被訪問到。
3. UIAccessibility
UIAccessibility是iOS的Accessibility最核心的一套API,它實際上是一個非正式協(xié)議,里面提供了一系列的方法來提供UI的輔助信息。舉個例子,在開啟了VoiceOver的情況下,在用戶觸摸到某個UI的時候,APP就會將這些方法提供的關于此UI的輔助信息轉(zhuǎn)化成語音念出來。
這里有必要說一下什么是非正式協(xié)議:
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.
非正式協(xié)議實際上是基于NSObject
的一個分類,它的子類們都隱式地遵守了該協(xié)議。在非正式協(xié)議中聲明的方法,可以選擇實現(xiàn)與否。這就很清楚了,在還沒有引入“optional protocol methods”之前,非正式協(xié)議就是一種替代方案。
3.1 UIAccessibility API
回到UIAccessibility,它里面有幾個比較重要并且常用的API:
- accessibilityLabel:一個簡短的本地化的詞或短語,簡潔地描述了控件或視圖,但不能識別元素的類型。 例如“添加”或“播放”
- accessibilityHint:一個簡短的本地化短語,用于描述作用于元素上的操作所得到的結(jié)果。 例如“添加標題”或“查看購物車”
- accessibilityTraits:一個或多個單獨特征的組合,每個特征描述元素的某一個方面,包括狀態(tài)、行為或用法。這些都定義成了UIAccessibilityTrait的某一個常量。
- accessibilityFrame:屏幕中某元素的坐標和大小
- accessibilityValue:某UI元素的當前值,并且這個值無法由label表示出來。例如,某個slider的標簽可能是“速度”,但其當前值可能為“50%”
貼一張官方文檔的圖,便于理解:
[image:DD012E17-8603-4975-B796-DDEC36F39C70-30478-00017DF4F61F5B6F/accessibility.png]
3.2 UIAccessibility的正確使用
標準的UIKit controls和views都是自動無障礙的,所以只需要確保它們默認提供的無障礙信息是否準確。
如果寫了一個自定義的view為用戶提供信息,或者一個可以自定義的可以交互的控件,那么你就需要自己實現(xiàn)它的無障礙配置。這就是我們常見的情況了,這種情況又可以分為以下兩種:
- an individual view:這個view里面不包含任何子元素
- a container view:這個view里面包含其他子元素,并且這些子元素都需要做無障礙配置
3.2.1 Make Custom Individual Views Accessible
// 這個view為某個自定義視圖的實例
// 方式一:直接給屬性賦值
view.isAccessibilityElement = YES
// 方式二:實現(xiàn)UIAccessibilityProtocol協(xié)議中的方法
- (BOOL)isAccessibilityElement {
return YES;
}
3.2.2 Make the Contents of Custom Container Views Accessible
這種情況是:你有一個視圖容器,里面包含了很多的其他元素,而這些元素提供了某些信息或者可以進行交互,也就是說這些子元素需要配置成accessible,而你的視圖容器不需要配置成accessible。
在此種情況下,你的視圖容器就需要實現(xiàn)UIAccessibilityContainer
協(xié)議,該協(xié)議中的方法會將這些需要配置成accessible的子元素放在一個數(shù)組中。
// 這里我們假定這些子元素都沒有默認實現(xiàn)UIAccessibility協(xié)議,這時就需要用到UIAccessibilityElement
@implementation ContainerView
- (NSArray *)accessibleElements {
if ( _accessibleElements != nil ) {
return _accessibleElements;
}
_accessibleElements = [[NSMutableArray alloc] init];
// 每一個子元素都是一個UIAccessibilityElement,將他們?nèi)继砑拥絖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協(xié)議方法
- (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進行配置開發(fā)的時候,其實最重要的就是保證你所傳達的信息的準確可靠,并在此前提下盡量保持信息的簡潔。如何滿足這個要求?就是深入理解蘋果公司對UIAccessibility中API的定義,最重要的就是:accessibilityLabel,accessibilityHint,accessibilityTraits,accessibilityValue。這一部分在上文已經(jīng)簡單解釋過了,就不贅述了。這里只列出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 動態(tài)變化
// 這個方法的用處:當你的界面中某些元素是動態(tài)的,比方說hidden之類的,它們發(fā)生變化時,可以用該方法發(fā)送通知
// 此時屏幕上的accessible元素會變成element這個元素,即接受通知的元素
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, element);
4.2 UIAccessibilityAction
這個非正式協(xié)議里面提供了一些函數(shù),用來支持一些比較特殊的需要在Accessibility情況下完成的行為。
這里列出比較常用的三個函數(shù):(Magic Tap單獨提出來)
// 默認的雙擊之后會觸發(fā)的函數(shù)
- (BOOL)accessibilityActivate;
// 增加(減少)此元素的accessibilityValue
- (void)accessibilityIncrement;
- (void)accessibilityDecrement;
Magic Tap
- (BOOL)accessibilityPerformMagicTap;
在某些情況下,我們的app會有一個最核心、重要的功能,比方說音樂播放器最重要的功能就是播放音樂,手機phone最重要的功能就是接聽電話,clock最重要的功能可能就是關閉鬧鐘了……那么在Voice Over的情況下,Magic Tap為我們提供了直接的手勢來觸發(fā)該功能,即“兩根手指輕點屏幕兩次”。也就是說,你在app的任何位置,只要做出了Magic Tap的手勢,就會觸發(fā)該函數(shù)。
需要注意的是:Magic Tap在你的App中應該只在AppDelegate文件中實現(xiàn)一次,這樣才滿足Magic Tap的設定和意義。(我嘗試了在其他文件中也實現(xiàn),但是并沒有被觸發(fā))
4.3 觸發(fā)事件
在Voice Over的情況下,選中到該元素后,通過上劃或者下劃來選擇需要觸發(fā)的事件(這個上劃下劃的手勢我也是摸索了好久才發(fā)現(xiàn))
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. 總結(jié)/經(jīng)驗
- iOS模擬器的Accessibility Inspector可以用來調(diào)試部分Accessibility功能
- 動畫,特別是交互動畫,和手勢觸摸相關的,這種沒有什么好的實現(xiàn)方法。想來也應該,動畫優(yōu)化的是視覺體驗,在但是使用Voice Over都是視力有所障礙的人群,沒必要實現(xiàn)這一部分功能
- Accessibility旨在為有障礙人群提供準確簡潔的信息,以方便他們使用App。那么如何提供信息,信息是聚合還是分散,這在不同情況下需要做出不同的選擇,需要配置為accessible的元素也需要視情況而定
- Accessibility的配置并不復雜,且跟UI高度相關,建議在寫UI的同時考慮一下Accessibility的問題,如果需要配置為accessible,那么可以順手完成