帶你認識——iOS Accessibility

1. Accessibility是什么

在Apple的定義中,我們可以理解為:無障礙使用。所謂“無障礙使用”,是對于“有障礙人士來說”,比方說讓一位有眼疾的用戶可以“無障礙”使用iPhone上的APP。
iOS的設備天生就具備這一特性,打開“設置 -> 通用 -> 輔助功能”,就可以看見Apple提供的一系列的Accessibility功能,這里可以說幾個比較常見的:

  1. VoiceOver:VoiceOver是蘋果創(chuàng)新的屏幕閱讀技術,用戶通過觸摸屏幕,隨后APP返回語音信息,從而引導用戶操作
  2. 縮放(Zoom):顧名思義,縮放屏幕上的元素
  3. 顯示調(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主要包括以下幾個部分:

  1. UIAccessibility(Informal Protocol):實現(xiàn)UIAccessibility協(xié)議的對象報告其可訪問性狀態(tài)(即是否可訪問),并提供有關其自身的描述性信息。 默認情況下,標準的UIKit控件和視圖實現(xiàn)了UIAccessibility協(xié)議
  2. UIAccessibilityContainer(Informal Protocol):該協(xié)議一般用于UIView的子類,可以讓它所包含的一些子UI作為單獨的元素被訪問到。當一個視圖所包含的子對象并不是UIView的子類,而又需要被訪問到的時候,這個協(xié)議就非常有用。
  3. 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:

  1. accessibilityLabel:一個簡短的本地化的詞或短語,簡潔地描述了控件或視圖,但不能識別元素的類型。 例如“添加”或“播放”
  2. accessibilityHint:一個簡短的本地化短語,用于描述作用于元素上的操作所得到的結(jié)果。 例如“添加標題”或“查看購物車”
  3. accessibilityTraits:一個或多個單獨特征的組合,每個特征描述元素的某一個方面,包括狀態(tài)、行為或用法。這些都定義成了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為用戶提供信息,或者一個可以自定義的可以交互的控件,那么你就需要自己實現(xiàn)它的無障礙配置。這就是我們常見的情況了,這種情況又可以分為以下兩種:

  1. an individual view:這個view里面不包含任何子元素
  2. 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的各種特征值:(正確使用尤為重要)

  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 動態(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)驗

  1. iOS模擬器的Accessibility Inspector可以用來調(diào)試部分Accessibility功能
  2. 動畫,特別是交互動畫,和手勢觸摸相關的,這種沒有什么好的實現(xiàn)方法。想來也應該,動畫優(yōu)化的是視覺體驗,在但是使用Voice Over都是視力有所障礙的人群,沒必要實現(xiàn)這一部分功能
  3. Accessibility旨在為有障礙人群提供準確簡潔的信息,以方便他們使用App。那么如何提供信息,信息是聚合還是分散,這在不同情況下需要做出不同的選擇,需要配置為accessible的元素也需要視情況而定
  4. Accessibility的配置并不復雜,且跟UI高度相關,建議在寫UI的同時考慮一下Accessibility的問題,如果需要配置為accessible,那么可以順手完成

參考文檔:Accessibility Programming Guide for iOS

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內(nèi)容

  • 介紹 1.開啟voiceOver 設置->通用->輔助功能->VoiceOver 在voiceOver模式中 a....
    linatan閱讀 6,123評論 0 8
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 本文為大地瓜原創(chuàng),歡迎知識共享,轉(zhuǎn)載請注明出處。雖然你不注明出處我也沒什么精力和你計較。作者微信號:christg...
    大地瓜123閱讀 328評論 0 0
  • 一聲呼喊,我心蕩漾 初衷總是好的,最開始我想大學社團會帶給我什么?可以是技能,可以是機遇,可以是挑戰(zhàn)。而此刻...
    云凡夫子閱讀 218評論 0 0
  • 他為戲子,她是看官。 他盡情表演,她卻為他流淚 他曾擁有戲臺最熱烈的掌聲 卻因她一句卸下了偽裝 一生只為一人的捧場...
    說與鬼聽閱讀 512評論 2 15