VoiceOver是蘋果「讀屏」技術的名稱,屬於輔助功能的一部分。VoiceOver能夠讀出屏幕上的信息,以幫助盲人進行人機交互。 這項技術在蘋果的各個系統中均可以看到,OS X,iOS,watchOS,甚至tvOS。 蘋果公司的VoiceOver在2015年6月18日得到了美國盲人基金會(American Foundation for the Blind, AFB)頒發的海倫凱勒成就獎,成爲全球首家得到此殊榮的科技公司。 單從iOS來講,iOS的VoiceOver功能能夠絕不誇張的說是三大移動平臺中作的最好的。數組
雖說蘋果默認的UI組件都已經默認支持VoiceOver功能了,可是一般狀況下App仍是須要對VoiceOver進行適配和優化的,好比說一些自定義複雜UI組件。app
iPhone上開啓VoiceOver功能後,就能夠經過單指左右輕掃來遍歷當前界面中的全部的AccessibilityElement(能夠被VoiceOver訪問的UI元素),當一個AccessibilityElement被選中後,VoiceOver會將AccessibilityElement的信息讀出來。單指輕點兩次可以激活當前元素對應的操做,好比當前AccessibilityElement是一個按鈕,那麼對應的就是按鈕的Action事件。函數
簡單點來講在App開發過程當中關於VoiceOver咱們須要關注以下幾點:優化
界面上的AccessibilityElement有哪些設計
AccessibilityElement的位置和形狀code
AccessibilityElement的信息是什麼(就是Element被選中後,被讀出來內容)orm
AccessibilityElement所能響應的的事件有哪些事件
UIKit中的控件基本都是 VoiceOver Ready的,即便是UIView,你也能夠經過簡單的設置其實變成AccessibilityElement。因此這一小節中所講的AccessibilityElement其實都是UIView或其子類的實例。 element
相關屬性和方法基本都在UIAccessibility.h
這個頭文件中進行了聲明。開發
因此簡單的狀況下經過UIView的isAccessibilityElement
屬性就能夠控制某個View是不是AccessibilityElement,在UIKit的控件中,像UILabel,UIButton 這些控件的isAccessibilityElement屬性默認就是true的,UIView這個屬性默認是false。
通常狀況下AccessibilityElement的位置和形狀是經過accessibilityFrame進行設置的,默認值是View在屏幕中的位置,形狀就是View的矩形形狀。若是你想本身設置accessibilityFrame的值,那麼得注意下,這邊的frame值是相對於設備Screen的座標系的,固然能夠經過UIAccessibilityConvertFrameToScreenCoordinates函數來幫助轉換。此函數有兩個參數,一個rect,一個是view, 其含義就是將相對於view這個座標系的rect轉換成相對於screen座標系的值並返回。因此通常狀況下 rect能夠是目標Element在父View中的frame,view就爲其父view。
public func UIAccessibilityConvertFrameToScreenCoordinates(rect: CGRect, _ view: UIView) -> CGRect
若是你想設置非矩形的形狀,你也能夠經過給accessibilityPath屬性指定一個UIBezierPath類型的值來自定義AccessibilityElement的形狀。
至於AccessibilityElement的信息能夠經過下面幾個UIAccessibility的屬性來決定
accessibilityLabel 這是什麼
accessibilityHint 這個有什麼用,會產生什麼樣的結果
accessibilityValue 這個的值是什麼
accessibilityTraits 這個的類型以及狀態,就是經過traits來表徵這個Element的特質,數據類型是一個枚舉類型,能夠經過按位或的方式合併多個特性。
這裏有個須要注意的就是,當某個View的是AccessibilityElement的時候 ,其subviews都會被屏蔽掉,這個特性有時候仍是有用的,好比一個View中包含多個Label,那麼你但願每個下面的Label不要單獨能夠訪問到,那麼你能夠將這個View設置成能夠訪問的,而後將其accessibilityLabel設置爲全部子Label的accessibilityLabel的合併值。
至於AccessibilityElement的事件,最簡單的莫過於上面提到單指輕點兩次可以激活當前元素對應的操做了,若是當前AccessibilityElement實現的public func accessibilityActivate() -> Bool
這個方法返回true,那邊此邏輯將被調用,不然至關於在AccessibilityElement的accessibilityActivationPoint這個位置點上進行了一次Tap操做。
#### Accessibility Container 設想下這樣的一個場景,一個UIView,內部包含一組用戶能夠進行交互的內容,每個內容之間是獨立的,可是這些內容不是以子View的形式存在,而是經過Quarz 2D或者Core Text渲染而成,因此這部份內容沒法經過上面的方式變成AccessibilityElement。這種狀況UIView須要按照UIAccessibilityContainer的方式,來將內部的每個獨立的內容都描述成UIAccessibilityElement的實例,這個時候這個UIView咱們稱之爲Accessibility Container。
接下來說解具體的實現步驟了。 先說說iOS 8以後如何實現,首先Accessibility Container的isAccessibilityElement值必須設置爲false,另外咱們須要建立出全部的UIAccessibilityElement的實例,而後賦給accessibilityElements屬性(iOS 8.0+) 假設在一個UIView的子類中,經過叫作updateAccessibleElements的方法來更新維護全部的UIAccessibilityElement實例,當界面上的內容發生變化,或者VoiceOver開啓關閉狀態發生變化時,調用此方法以更新accessibilityElements,相關僞代碼以下:
internal func updateAccessibleElements() { guard UIAccessibilityIsVoiceOverRunning() else { self.accessibilityElements = nil return } self.isAccessibilityElement = false var elements = [AnyObject]() let element1 = UIAccessibilityElement(accessibilityContainer: self) element1.accessibilityLabel = "element1" element1.accessibilityTraits = UIAccessibilityTraitStaticText element1.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(element1FrameInSelf, self) elements.append(element1) ... self.accessibilityElements = elements }
若是是iOS 8如下,那麼就須要實現下面的幾個方法來實現了,比較繁瑣:
// accessibilityElement的個數 public func accessibilityElementCount() -> Int // 返回指定Index的accessibilityElement public func accessibilityElementAtIndex(index: Int) -> AnyObject? // 返回指定accessibilityElement的Index public func indexOfAccessibilityElement(element: AnyObject) -> Int
使用上面第二種方式的時候,每每須要本身維護一個包含全部accessibilityElements的數組,而後經過數組來完成上面的這三個方法的返回。這種方法比較累贅,並且當界面更新須要刷新內容的時候,還須要發送通知系統,告訴系統界面上的accessibilityElements有變更須要更新。因此最小版本定位於iOS 8的狀況下仍是直接設置accessibilityElements屬性的方式比較科學。
關於UIAccessibilityElement這個類的設計仍是存在一些疑惑的,既然NSObject的UIAccessibility擴展已經包含了諸如accessibilityLabel
,accessibilityHint
這些屬性,爲什麼UIAccessibilityElement類中還須要重複聲明這些屬性,有點重複的感受。
以前只講到了最簡單的事件,就是單指輕點兩下,其實常見的Actions有下面這些,每個Action都會對應一個方法,能夠經過覆蓋方法的方式來自定義Action對應的邏輯:
Activate 單指輕點兩次 public func accessibilityActivate() -> Bool
Escape. 單指 Z-shaped 手勢通常用於退出模態界面或者返回導航的上一頁界面 public func accessibilityPerformEscape() -> Bool
Magic Tap. 雙指輕點兩次觸發 most-intended action. public func accessibilityPerformMagicTap() -> Bool
Three-Finger Scroll. 三指滑動觸發界面水平或者垂直的滾動 public func accessibilityScroll(direction: UIAccessibilityScrollDirection) -> Bool
Increment. 單指向上滑動,須要設置accessibilityTraits爲UIAccessibilityTraitAdjustable,不然對應的方法不會被調用 public func accessibilityIncrement()
Decrement. 單指向下滑動,須要設置accessibilityTraits爲UIAccessibilityTraitAdjustable,不然對應的方法不會被調用 public func accessibilityDecrement()
這些方法中,其中Escape,Magic Tap,Three-Finger Scroll這幾種手勢支持在響應鏈中向上尋找對應的Action方法,首先用戶在屏幕上進行相應的手勢,系統檢查當前VoiceOver的Focus的Element有無實現對應的方法,沒有實現的話,則向響應鏈的上一級尋找。好比有些全局性的操做,對應的方法寫在上層View或者ViewController中比較合適。 其實不少系統提供的組件都默認實現了一些VoiceOver的手勢Action,好比UINavigationController, UIAlertController 都提供了對Escape手勢的支持。
Accessibility提供了一系列的通知,能夠完成一些特定的需求。好比你能夠監聽UIAccessibilityVoiceOverStatusChanged通知,來監控Voice Over功能開啓關閉的實時通知
。
或者是你在App中主動發送一些通知,來讓系統作出一些變化,好比當你界面上的AccessibilityElement有變更的時候你能夠發送UIAccessibilityLayoutChangedNotification
通知,通知發送時使用UIAccessibility的專用的函數,UIAccessibilityPostNotification函數有兩個參數,第一個是通知名,第二個是你想讓VoiceOver讀出來的字符串或者是新的VoiceOver的焦點對應的元素。
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.myFirstElement)
AccessibilityElement的信息儘可能簡潔,accessibilityLabel不要包含提示性質的文案,避免信息干擾
TableView的每個Cell的信息儘可能合併,使得Cell變成一個總體的AccessibilityElement,避免無心義的冗餘元素之間切換的操做。Cell中有多個按鈕的時候,能夠考慮使用Magic Tap的方式,Magic Tap的Action中彈出sheet樣式的UIAlertController來供用戶操做。
自定義的模態頁面注意設置accessibilityViewIsModal爲true,最好支持Escape手勢 的方式退出模態頁面。
將頁面中裝飾用的沒有實際意義的元素的accessibilityElementsHidden設置成true,減小操做過程當中的干擾。