其實很少用戶知道,ios系統其實有一套完整的輕鬆訪問機制,很多盲人或者說視障用戶都喜歡使用蘋果手機。
所以說,作為一名開發者,我覺得不論是從產品銷售面還是作為責任,都應該做好完善的輕鬆訪問支持。
不過好在,得益於蘋果嚴格的開發規範,所以一般只要你的app已經通過審核能夠上架,那麼基本上 VoiceOver 就已經能夠很好的識別你 app 中的大部分內容了,比較通用的,比如 tabView,比如 navigationController,這些通用的框架都已經自動支持了的。現在,我們就一起來看看,如何讓你的 app 利用好 VoiceOver 的所有高級功能,這些功能能夠讓你在一定程度上控制 VoiceOver,讓它更好地兼容你的app,讓用戶體驗更佳完美。
主要目標
VoiceOver 除了能夠讀出屏幕內容外,其實還可以做更多的內容,比如與界面交互,比如內置的魔法輕拍,魔法返回,要讓我們自定義的內容支持它,就需要自己去重寫對應的方法,還要讓我們自己定制的按鈕,按照希望的方式來運作——畢竟使用 VoiceOver 的用戶是靠聽的,所以有時候我們還需要為按鈕等加上更多解釋,甚至,在一些動作之後,還要讓 VoiceOver 讀出特定的內容來告知用戶都發生了什麼,把這些都規範了,那麼視障用戶也能輕鬆地使用你開發的 app 了。
VoiceOver 運行狀態
要對 VoiceOver 進行支持,那麼得先要能發現它的狀態——雖然其實大部分情況下你不需要這麼做,因為你在對 VoiceOver 做多配置,如果 VoiceOver 不開啟的話,就不會有任何影響,但如果你希望檢測一下的話,在 UIKit 裡有對應的全局函數:
1 |
var isVoiceOverOn:Bool {return UIAccessibilityIsVoiceOverRunning()} |
當然,你也可以不用像我這樣在封裝一層,我是為了自己整體代碼看起來更一致罷了。
設定可讀
對於一個自定義的控件,你需要手動地來為它指定是否要作為一個 VoiceOver 控件:
1 |
someButton.isAccessibilityElement = true |
控件角色
ios 為控件提供了一些常見的角色可以配置,比如按鈕,比如音量鍵,為你的控件設定合適恰當的控件角色,這有助於 VoiceOver 為你的控件提供對應的額外功能。
舉例來說,對於鍵盤按鈕,就要給按鈕去掉按鈕角色而使用鍵盤按鈕角色,只有這樣,用戶才能實現用手指在鍵盤上滑動,然後找到需要的按鈕時,鬆開手指立即執行按下的操作。
1 |
accessibilityTraits = UIAccessibilityTraitKeyboardKey |
當然,其實這些角色可以是多個,我們通過最後的聲明可以看到這個角色類型其實就是一個大的整數: 上市 typealias UIAccessibilityTraits = UINT64 所以我們可以這樣:
1 |
accessibilityTraits = UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitButton |
這裡我順便為大家翻譯一下聲明里的翻譯,具體的特性需要大家自己去嘗試。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
// 不使用角色,在某些情况下很有用,比如你想自己处理它的时候,落格输入法的候选字,既想让它有按钮的动作,又不想让它读出来“按钮”这个烦人的提示,那么就用这个了。 public var UIAccessibilityTraitNone: UIAccessibilityTraits // 如果是系统拖的按钮,那么默认肯定就是这个。如果你给按钮的 VoiceOver title 设定的名字是xx按钮,那么VO就不会给你追加“按钮”俩字。 public var UIAccessibilityTraitButton: UIAccessibilityTraits // 控件被用于链接的时候使用 public var UIAccessibilityTraitLink: UIAccessibilityTraits // 控件作为内容的标题的时候使用,比如说导航栏的标题。 @available(iOS 6.0, *) public var UIAccessibilityTraitHeader: UIAccessibilityTraits // 搜索栏 public var UIAccessibilityTraitSearchField: UIAccessibilityTraits // 图片,可以和按钮、链接之类的混合使用。 public var UIAccessibilityTraitImage: UIAccessibilityTraits /* 被选中的控件。 比如说tableview里被选中的那一行。 */ public var UIAccessibilityTraitSelected: UIAccessibilityTraits // 当控件自身可以播放声音的时候 public var UIAccessibilityTraitPlaysSound: UIAccessibilityTraits // 当控件是键盘按钮的时候 public var UIAccessibilityTraitKeyboardKey: UIAccessibilityTraits // 不能改变的静态文本 public var UIAccessibilityTraitStaticText: UIAccessibilityTraits /* 当作为提供当前情况快速总结的时候。 比如说,一个天气应用,第一次打开时当天的天气情况就可以标记为这个。 */ public var UIAccessibilityTraitSummaryElement: UIAccessibilityTraits // 控件不可用的时候 public var UIAccessibilityTraitNotEnabled: UIAccessibilityTraits /* 当控件要频繁地更新其label或者值的时候 比如说秒表。 */ public var UIAccessibilityTraitUpdatesFrequently: UIAccessibilityTraits /* 当控件启动不应该被VO干扰的媒体会话的时候(比如说播放电影,录音) */ @available(iOS 4.0, *) public var UIAccessibilityTraitStartsMediaSession: UIAccessibilityTraits /* 当控件可以被“调节”的时候,比如说滑动条。 控件也要实现 accessibilityIncrement 和 accessibilityDecrement. */ @available(iOS 4.0, *) public var UIAccessibilityTraitAdjustable: UIAccessibilityTraits // 当控件需要直接与用户交互的时候使用,比如说钢琴键盘。 @available(iOS 5.0, *) public var UIAccessibilityTraitAllowsDirectInteraction: UIAccessibilityTraits /* 通知VO完成阅读时要滚动到下一个页面。VO会调用 accessibilityScroll: with UIAccessibilityScrollDirectionNext 并且在检测到内容不变时停止。 */ @available(iOS 5.0, *) public var UIAccessibilityTraitCausesPageTurn: UIAccessibilityTraits /* Used when a view or accessibility container represents an ordered list of tabs. The object with this trait should return NO for isAccessibilityElement. */ @available(iOS 10.0, *) public var UIAccessibilityTraitTabBar: UIAccessibilityTraits |
魔法輕拍
這是一個很常用的手勢,就是雙指在屏幕上雙擊。比如說在落格輸入法中,它用來快速返回第一個候選(當你往後翻了很久的時候)。如果你想監聽這個動作,就在對應的 Controller 裡重寫 accessibilityPerformMagicTap :
1 2 3 |
override func accessibilityPerformMagicTap() -> Bool { ... } |
返回 true 來告訴系統,VoiceOver 接收了操作,返回 假 來說明跳過,將內容髮送給其他接受者。
魔法返回
魔法返回的手勢有點難了,但稍微練習一下應該也不是什麼難事,總之,在能夠返回的地方比如 navigationController 中雙指左右來回一下就是執行了一次魔法返回。還是用落格輸入法舉例子,那麼就是在鍵盤上執行魔法返回來收起鍵盤:
1 2 3 4 |
override func accessibilityPerformEscape() -> Bool { dismissKeyboard() return true } |
手動發出提示
那麼,怎麼才能主動給用戶發出內容提示呢?比如“進入表情鍵盤”?那麼你可以這樣:
1 |
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, "进入表情键盘"); |
那麼VO就會在你調用它發送通知後讀出你的內容,所以,記得要做本地化。
同時,值得一提的是,如果你同時發送多條通知,那麼後面的通知會把前邊的通知給覆蓋掉!比如這樣:
1 2 3 |
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, "进入表情键盘"); UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, "进入符号键盘"); UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, "进入拼音键盘"); |
那麼你得到的很可能是“進入表情鍵盤”或“進入拼音鍵盤”,其它的兩個就丟失了。
還有比如說在落格輸入法之前的版本中,一直存在一個bug,那就是VO裡無法讀出刪除的字。這對於視障用戶來說是一件很重要的問題,因為如果你不讀出來的話他們就不能確定自己刪除了啥,那可真是糟糕透了。而且比這還糟糕的是,我自己本地測試是完全正常的!
所以說,一定要注意這個問題,發送通知不需要一定要在主線程(如果你和我一樣常用並發的話),但一定要注意間隔,經過分析,最終我才明白,當你在鍵盤上點擊了刪除,那麼作為一款第三方鍵盤,我必須調用內置的api來刪除,可是這個刪除會觸發UI改變(因為文字變了對吧?)VoiceOver就會去傻傻地報告光標位置,顯然,一般輸入錯了肯定是最後一個字居多,那麼自然bug就成了讀“文本底部”。
這時候,只需要比系統的通知慢就好了,(系統通知比你發的要慢一點,所以它老是覆蓋我的通知即使我把它放在了刪除調用之後),所以,我這樣處理了:
1 2 3 |
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, String(word.characters.last!)); }) |
總之,慢這麼0.1秒對用戶來說他們發現不了什麼區別,但對於系統來說,那就是有了足夠的時間讓VO在收到系統的通知後,還來不及讀出就被你的通知給覆蓋掉了。
手動改變焦點
當用戶點了鍵盤,或者說做了一些操作之後,我們需要讓VO自動將焦點移動到上邊,典型的情況就是你在點擊了鍵盤按鈕後,候選欄會更新新的候選詞,這個時候講焦點移動到第一個候選就會很好用。因為這樣的話,用戶就可以雙擊屏幕直接選中它了。所以,我這樣做:
1 |
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self); |
第二個實際參數就是要獲得焦點的控件引用,這樣收到通知後,VO會立即將焦點移動到這個空間上並讀出它的內容。
總結
基本上來說,對於 VoiceOver 的測試,你是無法在 xcode 中完成的,它所附帶的工具僅僅能夠支持最簡單的檢查,想要完整地測試你的VO支持,你應該把app安裝到你的 iphone 上,然後打開VoiceOver,閉上眼睛去親自使用一番,如果不擅長 VoiceOver 的你都能順利地使用你的app,那應該就沒什麼大問題了。
以上這些,就是我把落格輸入法做成 ios 平台 VoiceOver 支持最好的中文輸入法的人生經驗了。?
本文由 落格博客 原創撰寫:落格博客 » ios 為視障用戶支持 VoiceOver
轉載請保留出處和原文鏈接:https://www.logcg.com/archives/2322.html
註釋