近期在處理App適配VoiceOver(旁白)功能,簡(jiǎn)單的系統(tǒng)控件蘋果都能很好的識(shí)別,但在復(fù)雜界面處理遇到了一些問題,VoiceOver問題可參考的資料不多,把自己踩過的坑記錄如下。
首先介紹下 旁白常用的手勢(shì):
- 輕點(diǎn):選擇并朗讀項(xiàng)目。
- 左右輕掃:選擇下一項(xiàng)或上一項(xiàng)。
- 雙指輕點(diǎn)三下:打開“項(xiàng)目選取器”。
- 三指上下輕掃:一次滾動(dòng)一頁。
- 三指左右輕掃:前往下一頁或上一頁(例如,在主屏幕上中)。
- 四指滑動(dòng):切換App 。
這是本人在開發(fā)旁白模式中常用的手勢(shì),更多手勢(shì)可以查看 iPhone使用手冊(cè) 來了解。
旁白最常用 UIAccessibility API
- accessibilityLabel: 描述內(nèi)容“是什么”,例如 “產(chǎn)品名稱”
- accessibilityValue: 描述“值是多少”,例如“年化利率5%”
- accessibilityTraits: 描述"特性、狀態(tài)、行為、用法"??丶奶匦钥梢允前粹o(Button),控件的狀態(tài)可以是已選中(Selected)
- accessibilityHint: 描述行動(dòng)“做什么”,例如“點(diǎn)擊兩下可跳轉(zhuǎn)購買頁面”
1、Label、Value、Hint 配置的字符串,會(huì)以語音的形式念出來,配置什么,念什么。
2、 配置的Traits各個(gè)類型,也會(huì)以語音的形式念出。例如:UIAccessibilityTraitSelected、UIAccessibilityTraitImage、UIAccessibilityTraitButton分別播報(bào)"已選定”、”圖片”、”按鈕”。
遇到的問題記錄:
1.是否啟用VoiceOver
UIAccessibilityIsVoiceOverRunning()
2.主動(dòng)播報(bào)
在toast 彈出或者刷新成功等場(chǎng)景需要主動(dòng)播報(bào)
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @"播報(bào)文案xxxx");
3.焦點(diǎn)穿透
accessibilityViewIsModal 屬性設(shè)為YES
self.noNetworkV.accessibilityViewIsModal = YES;
4.兼容開發(fā)者自定義的控件和視圖,支持VoiceOver
所有的標(biāo)準(zhǔn)UIKit控件和視圖默認(rèn)支持VoiceOver,UILabel,UIButton這些控件的isAccessibilityElement屬性默認(rèn)是true, UIView屬性默認(rèn)是false,所以自定義視圖如果想要支持VoiceOver,需要設(shè)置 isAccessibilityElement = YES, 使得VoiceOver可以訪問這個(gè)view。
view.isAccessibilityElement = YES;
view.accessibilityLabel = @"文案內(nèi)容";
view.accessibilityTraits = UIAccessibilityTraitNone;
5.訪問Cell與Cell中的按鈕,且分別讀出不同的內(nèi)容
假設(shè)現(xiàn)在有-個(gè)UITableview與一個(gè)自定義Cell,,Cell中有一個(gè)按鈕,現(xiàn)在需求是可以訪問Cell與Cell中的按鈕,且分別讀出不同的內(nèi)容,如下圖所示意:

那么此時(shí)單純?cè)O(shè)置Cell為訪問元素然后再訪問子元素這個(gè)思路是行不通的,我們可以設(shè)置CelI不激活為isAccessibilityElement, 但是蘋果內(nèi)部是可以將Cell作為整個(gè)訪問元素且設(shè)置它的accessibilityL abel,完成button基本設(shè)置后, 再設(shè)置button的accessibilityHint就可以實(shí)現(xiàn)我們想要的效果。
核心代碼如下:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TestCell *cell = [tableView dequeueReusableCellWithIdentifier:[TestCell description] forIndexPath:indexPath];
[cell setDataWithVersionM:self.dataArray[indexPath.row]];
cell.isAccessibilityElement = NO;
return cell;
}
.....
- (void)setDataWithVersionM:(YBUserVersionM *)versionM {
if ([versionM.versionCode isEqualToString:@"default"]) {
self.accessibilityLabel = @"標(biāo)準(zhǔn)版";
} else if ([versionM.versionCode isEqualToString:@"young"]) {
self.accessibilityLabel = @"鄉(xiāng)村版";
} else if ([versionM.versionCode isEqualToString:@"big"]) {
self.accessibilityLabel = @"大字版";
}
NSString *chooseOrNotStr;
if ([versionM.versionCode isEqualToString:[[YBVersionManager instance] getCurentVersion]]) {
[self.chooseBtn setSelected:YES];
chooseOrNotStr = @"已選中";
} else {
[self.chooseBtn setSelected:NO];
chooseOrNotStr = @"未選中";
}
self.accessibilityHint = chooseOrNotStr;
}
6. 只有背景圖片無文字的控件.
需要給出描述和對(duì)應(yīng)控件的屬性, 例如返回按鈕/關(guān)閉按鈕

self.backButton.accessibilityLabel = @"返回";
self.closeButton.accessibilityLabel = @"關(guān)閉";
self.closeButton.accessibilityTraits = UIAccessibilityTraitButton;
7.數(shù)字/電話號(hào)連讀處理,比如播報(bào)驗(yàn)證碼1234的時(shí)候,系統(tǒng)會(huì)自動(dòng)播報(bào)成“一千兩百三十四”。
① 在中間加分隔符 1:2:3:4,這樣就會(huì)一個(gè)數(shù)字一個(gè)數(shù)字的讀了;
② 轉(zhuǎn)換為對(duì)應(yīng)的大寫漢字即可;
self.cardNubLbl.accessibilityLabel = [YBVoiceOverTool acquireAccessableNumber:bankNum];
/// 將電話號(hào)碼轉(zhuǎn)換為旁白可讀的中文
+ (NSString *)acquireAccessableNumber:(NSString *)number {
NSArray *transFromArr = @[@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"0"];
NSArray *transToArr = @[@"一",@"二",@"三",@"四",@"五",@"六",@"七",@"八",@"九",@"零"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:transToArr forKeys:transFromArr];
NSMutableArray *resultArr = [NSMutableArray array];
for (int i = 0; i < number.length; i++) {
NSString *currentStr = [number substringWithRange:NSMakeRange(i, 1)];
[resultArr addObject:dictionary[currentStr]?:currentStr];
}
NSString *resultStr = [resultArr componentsJoinedByString:@""];
return resultStr;
}
8.調(diào)整頁面焦點(diǎn)順序
cell.shouldGroupAccessibilityChildren = YES;
cell.accessibilityElements = @[cell.leftBtn, cell.rightBtn];
注意: 調(diào)整層級(jí)時(shí)不要漏掉控件,否則旁白模式下這個(gè)被遺漏的控件會(huì)無法選中也無法播報(bào)。
9. 多音字讀錯(cuò)
解決方案:換位同音字替代。比如:“密碼重(chóng)置”,蘋果識(shí)別為“密碼重(zhòng)置”,可以用“密碼崇置” 來解決。accessibilityLabel 屬性僅僅旁白模式下朗讀,不影響展示。
self.tipsLbl.accessibilityLabel = @“密碼崇置”;
10.進(jìn)入語音界面,旁白音量超級(jí)小,幾乎聽不見
錄音要AVAudioSessionCategoryRecord模式,結(jié)束錄音設(shè)為AVAudioSessionCategoryPlayback
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
}
// 開始錄音
- (void)vprStartRecord {
// sdk開始錄音
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[[VoiceRecordManager sharedInstance] start];
}
// 結(jié)束錄音
- (void)vprStopRecordShowTips:(BOOL)isTips {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
// sdk結(jié)束錄音
[[VoiceRecordManager sharedInstance] stop];
....
}
注意:因?yàn)轫?xiàng)目中是進(jìn)入這個(gè)頁面旁白聲音就變小了,所以我在viewWillAppear 生命周期中也設(shè)置了setCategory:AVAudioSessionCategoryPlayback 模式。
11. 一段文案中有兩個(gè)跳轉(zhuǎn)事件:點(diǎn)擊隱私政策,只會(huì)跳轉(zhuǎn)第一個(gè),第二個(gè)一直跳轉(zhuǎn)無效
解決方案:
a、改成兩個(gè)控件,每個(gè)控件一個(gè)點(diǎn)擊事件;
b、如果是富文本的話,直接用\n 換行,變?yōu)閮尚姓故緟f(xié)議,可以分別跳轉(zhuǎn)兩個(gè)協(xié)議。
12. 支持卡片內(nèi)容三指滑動(dòng)

核心代碼:
- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
if (direction == UIAccessibilityScrollDirectionLeft) {
if (self.selectIndex < 4 ) {
self.selectIndex = self.selectIndex+1;
[self scrollToIndex:self.selectIndex];
[self accessibilityScrollPage:self.selectIndex];
return YES;
}
}else if (direction == UIAccessibilityScrollDirectionRight) {
if (self.selectIndex != 0) {
self.selectIndex = self.selectIndex-1;
[self scrollToIndex:self.selectIndex];
[self accessibilityScrollPage:self.selectIndex];
return YES;
}
}
return NO;
}