建置與稽核 SwiftUI、UIKit 和 AppKit 的無障礙功能,支援 VoiceOver、語音控制、切換控制、全鍵盤操作、動態字型、焦點還原、標籤/特徵/動作、遍歷順序、自訂轉輪、NSAccessibility、XCTest 檢查、適應性系統偏好設定,以及 App Store 無障礙營養標籤。適用於實作無障礙 UI、修正無障礙稽核問題、測試輔助技術行為,或為 App Store 無障礙營養標籤提供佐證。
iOS/macOS 無障礙 - SwiftUI、UIKit 與 AppKit
建置可搭配 VoiceOver、切換控制、語音控制、全鍵盤操作及適應性無障礙設定運作的 SwiftUI、UIKit 與 AppKit 介面。
目錄
- 核心原則
- VoiceOver 如何讀取元件
- SwiftUI 無障礙修飾詞
- 焦點管理
- 動態字型
- 自訂轉輪
- 系統無障礙偏好設定
- 裝飾性內容
- 語音控制
- 切換控制
- 全鍵盤操作
- 輔助使用 (iOS 18+)
- UIKit 無障礙模式
- AppKit 無障礙模式
- 無障礙自訂內容
- App Store 無障礙營養標籤
- 測試無障礙功能
- 常見錯誤
- 審查核對清單
- 參考資料
核心原則
- 優先使用語意化的系統控制項;自訂控制項必須暴露相同的名稱、角色、值、狀態與動作。
- 確保每個任務都能透過輔助輸入完成:不要讓手勢、顏色、動畫、懸浮狀態或視覺排列成為唯一傳達意義或操作的路徑。
- 在導覽、呈現、更新與關閉時,保持焦點與遍歷順序的明確性。
- 讓文字、版面、對比、透明度與動畫能根據使用者的無障礙設定自動調整。
- 將 App Store 無障礙宣告視為有證據支援的產品聲明,而非僅是單一控制項支援的摘要。
VoiceOver 如何讀取元件
VoiceOver 以固定且不可設定的順序讀取元件屬性:
標籤 -> 值 -> 特徵 -> 提示
設計標籤、值與提示時,請將此讀取順序納入考量。
SwiftUI 無障礙修飾詞
詳細的 SwiftUI 修飾詞範例(標籤、提示、特徵、分組、自訂控制項、可調整動作與自訂動作)請參閱 references/a11y-patterns.md。
焦點管理
焦點管理是大多數 App 失敗的地方。當工作表、提示或彈出視窗關閉時,VoiceOver 焦點必須回到觸發該元件的元素。
本節討論的是輔助技術的無障礙焦點。關於鍵盤焦點、方向性焦點、focusSection()、場景焦點值與 UIFocusGuide,請使用 focus-engine 技能。
在遍歷順序中稽核元件順序與分組;將鍵盤焦點機制導向 focus-engine。
@AccessibilityFocusState (iOS 15+)
@AccessibilityFocusState 是一個屬性包裝,用於讀取與寫入目前的無障礙焦點。它可搭配 Bool 用於單一目標焦點,或搭配可選的 Hashable 列舉用於多目標焦點。
struct ContentView: View {
@State private var showSheet = false
@AccessibilityFocusState private var focusOnTrigger: Bool
var body: some View {
Button("開啟設定") { showSheet = true }
.accessibilityFocused($focusOnTrigger)
.sheet(isPresented: $showSheet) {
SettingsSheet()
.onDisappear {
// 稍微延遲,讓轉場完成後再移動焦點
Task { @MainActor in
try? await Task.sleep(for: .milliseconds(100))
focusOnTrigger = true
}
}
}
}
}
使用列舉的多目標焦點
enum A11yFocus: Hashable {
case nameField
case emailField
case submitButton
}
struct FormView: View {
@AccessibilityFocusState private var focus: A11yFocus?
var body: some View {
Form {
TextField("姓名", text: $name)
.accessibilityFocused($focus, equals: .nameField)
TextField("電子郵件", text: $email)
.accessibilityFocused($focus, equals: .emailField)
Button("提交") { validate() }
.accessibilityFocused($focus, equals: .submitButton)
}
}
func validate() {
if name.isEmpty {
focus = .nameField // 將 VoiceOver 焦點移至無效欄位
}
}
}
自訂模態視窗
自訂覆蓋視圖需要 .isModal 特徵來限制 VoiceOver 焦點,以及一個逃脫動作來關閉:
CustomDialog()
.accessibilityAddTraits(.isModal)
.accessibilityAction(.escape) { dismiss() }
將關閉視為模態合約的一部分進行測試:使用者必須能夠透過相關輔助技術的逃脫手勢或鍵盤逃脫路徑來關閉覆蓋視圖,且焦點應回到觸發元件或下一個邏輯目標。
無障礙通知 (UIKit)
當需要在 UIKit 環境中宣告變更或強制移動焦點時:
// 宣告狀態變更(例如「項目已刪除」、「上傳完成」)
UIAccessibility.post(notification: .announcement, argument: "上傳完成")
// 部分畫面更新 -- 將焦點移至特定元件
UIAccessibility.post(notification: .layoutChanged, argument: targetView)
// 完整畫面轉換 -- 將焦點移至新畫面
UIAccessibility.post(notification: .screenChanged, argument: newScreenView)
動態字型
使用系統文字樣式來縮放文字。非文字尺寸也應縮放:圖示大小、間距、控制項高度與自訂點擊區域尺寸,若需追蹤文字大小,應使用 @ScaledMetric(relativeTo:)。
動態字型與適應性版面範例(包括 @ScaledMetric 與最小點擊目標模式)請參閱 references/a11y-patterns.md。
自訂轉輪
轉輪讓 VoiceOver 使用者能快速導覽至特定內容類型。為內容密集的畫面加入自訂轉輪。完整的轉輪範例請參閱 references/a11y-patterns.md。
系統無障礙偏好設定
務必尊重這些環境值:
@Environment(\.accessibilityReduceMotion) var reduceMotion
@Environment(\.accessibilityReduceTransparency) var reduceTransparency
@Environment(\.colorSchemeContrast) var contrast // .standard 或 .increased
@Environment(\.legibilityWeight) var legibilityWeight // .regular 或 .bold
減少動態效果
將基於移動的動畫替換為淡入淡出或無動畫:
withAnimation(reduceMotion ? nil : .spring()) {
showContent.toggle()
}
content.transition(reduceMotion ? .opacity : .slide)
檢查所有移動轉場,包括列刪除、數量變更、工作表或結帳呈現、以及模態關閉。在減少動態效果下,將滑動、彈跳、視差、彈簧與大型空間轉場替換為透明度變化、即時狀態變更或無動畫。
減少透明度、增加對比、粗體文字
// 減少透明度時使用實色背景
.background(reduceTransparency ? Color(.systemBackground) : Color(.systemBackground).opacity(0.85))
// 增加對比時使用更強烈的顏色
.foregroundStyle(contrast == .increased ? .primary : .secondary)
// 系統粗體文字啟用時使用粗體字重
.fontWeight(legibilityWeight == .bold ? .bold : .regular)
裝飾性內容
// 裝飾性圖片:對 VoiceOver 隱藏
Image(decorative: "background-pattern")
Image("visual-divider").accessibilityHidden(true)
// 圖示搭配文字:Label 會自動處理
Label("設定", systemImage: "gear")
// 僅圖示按鈕:必須有無障礙標籤
Button(action: { }) {
Image(systemName: "gear")
}
.accessibilityLabel("設定")
僅在圖片未提供相鄰可存取文字以外的資訊時,才將其視為裝飾性。如果圖片傳達了產品變體、狀態、圖表點、使用者產生的內容或其他區別細節,則應提供有意義的描述,而非隱藏它。
語音控制
語音控制依賴無障礙標籤來產生語音點擊目標。如果標籤遺失或無法朗讀,語音控制就無法定位該元件。
- 每個互動元件必須有可朗讀的無障礙標籤(不能只有表情符號或符號)。
- 在可見畫面中,標籤必須唯一 — 重複的標籤會強迫使用者透過疊加編號來區分。
- 將
accessibilityInputLabels視為凍結前的無障礙工作,用於處理較長、彆扭、本地化、多縮寫或常縮短的語音標籤;不要將其視為後續潤飾。語音控制與全鍵盤操作都會使用這些標籤。依重要性遞減順序列出替代方案。 - 廣泛對任何主要標籤難以說出的可見目標套用
accessibilityInputLabels,包括重複的列動作、數量控制項、帳號/設定連結、媒體控制項,以及帶有縮寫或產品名稱的本地化標籤。 - 啟用語音控制進行測試:說「顯示名稱」與「顯示編號」來確認所有互動元件都可被定位。
- 進行語音控制審查時,請驗證兩種疊加模式:「顯示名稱」確認可朗讀的標籤,「顯示編號」確認當名稱遺失、重複或彆扭時,每個可見的互動目標仍可被觸及。
accessibilityInputLabels 範例與可朗讀標籤指南請參閱 references/a11y-patterns.md。
切換控制
切換控制會依閱讀順序依序掃描無障礙元件。適當的分組與自訂動作對可用性至關重要。
- 使用
.accessibilityElement(children: .combine)將相關內容分組,以減少掃描停頓點。 - 每個掃描目標都應有意義且可操作。對 VoiceOver 隱藏的裝飾性元件也會對切換控制隱藏。
- 切換控制使用者無法執行滑動刪除、長按或多指手勢。請將這些互動暴露為
.accessibilityAction(named:)自訂動作 — 切換控制會將其顯示為選單。 - 具有非標準點擊區域的自訂控制項應確保
accessibilityFrame準確反映可點擊區域(用於點掃描模式)。
自訂動作與分組範例請參閱 references/a11y-patterns.md。
全鍵盤操作
全鍵盤操作(iOS/iPadOS 13.4+)讓使用者能用實體鍵盤導覽與操作 App。
稽核每個控制項是否可觸及、有標籤、有可見焦點,且無需觸控即可操作。將 Tab/方向性焦點、.focusable()、@FocusState、focusSection()、場景焦點值、tvOS 焦點與 UIFocusGuide 實作導向 focus-engine。
- 每個互動元件都可透過鍵盤觸及與啟動。
- 遍歷順序符合邏輯,且不會困住焦點。
- 在所有對比與文字大小設定下,焦點指示器保持可見。
- 僅手勢的行為有鍵盤可操作的替代方案。
- App 快捷鍵不會覆寫系統定義的快捷鍵,例如 Cmd+C、Cmd+V 或 Cmd+Tab。
全鍵盤操作稽核檢查請參閱 references/a11y-patterns.md。
遍歷順序
元件順序與分組必須遵循視覺與任務順序,並在 VoiceOver 滑動順序、切換控制掃描、語音控制疊加與全鍵盤操作審查中進行檢查。檢查遺失或重複的標籤、過多的列子元件、隱藏的自訂控制項、焦點陷阱,以及順序偏離任務的分組。將鍵盤或方向性路由機制保留在 focus-engine 中。
輔助使用 (iOS 18+)
輔助使用為認知障礙使用者提供簡化介面。App 應支援此模式:
// 檢查輔助使用是否啟用 (iOS 18+)
@Environment(\.accessibilityAssistiveAccessEnabled) var isAssistiveAccessEnabled
var body: some View {
if isAssistiveAccessEnabled {
SimplifiedContentView()
} else {
FullContentView()
}
}
主要指南:
- 減少視覺複雜度:更少的控制項、更大的點擊目標、更簡單的導覽
- 使用清晰、直白的語言作為標籤與說明
- 盡量減少一次呈現的選項數量
- 在「設定 > 輔助使用 > 輔助使用」中啟用輔助使用進行測試
UIKit 無障礙模式
對於自訂 UIKit 視圖,暴露有意義的元件、標籤、值、特徵與動作;使用 insert/remove 變更特徵;將自訂覆蓋視圖設為模態;並使用適當的宣告、版面變更或畫面變更通知。完整的 UIKit 範例請載入 references/a11y-patterns.md。
AppKit 無障礙模式
優先使用標準 AppKit 控制項。對於自訂 NSView 或虛擬元件,暴露正確的 NSAccessibility 角色、標籤、值、動作與狀態變更通知。NSAccessibilityElement 與自訂控制項範例請載入 references/a11y-patterns.md。
無障礙自訂內容
使用 .accessibilityCustomContent 提供有用的次要資訊,而不增加滑動停頓點;將高重要性保留給應自動讀取的內容。SwiftUI、UIKit 與 AppKit 範例請參閱 references/a11y-patterns.md。
App Store 無障礙營養標籤
關於 App Store 無障礙營養標籤、產品頁面聲明或 App Store Connect 無障礙答案,請閱讀 references/nutrition-labels.md。
在建議某項聲明之前,要求提供證據,證明使用者能在相關裝置類型上使用該功能完成所有常見任務。使用結構化的常見任務與無障礙功能對照表,在音訊內容需要字幕時包含媒體逐字稿,並明確警告 App Store 無障礙答案必須保持準確,不得視為行銷聲明。
測試無障礙功能
手動測試
- 無障礙檢查器:在模擬器與裝置上稽核標籤、特徵與對比。
- VoiceOver 測試:在「設定 > 輔助使用 > VoiceOver」中啟用。使用滑動手勢導覽每個畫面。
- 語音控制測試:在「設定 > 輔助使用 > 語音控制」中啟用。同時說「顯示名稱」與「顯示編號」;名稱驗證可朗讀的標籤,編號驗證即使名稱重複、遺失或彆扭,每個可見的互動目標仍可觸及。
- 全鍵盤操作測試:在「設定 > 輔助使用 > 鍵盤 > 全鍵盤操作」中啟用。使用 Tab 鍵遍歷每個畫面,確認所有互動元件都能取得焦點。
- 切換控制測試:在「設定 > 輔助使用 > 切換控制」中啟用。確認掃描順序符合邏輯,且自訂動作會出現在基於手勢的互動中。
- 動態字型:在「設定 > 輔助使用 > 顯示與文字大小 > 更大文字」中測試所有文字大小。
使用 XCTest 自動化測試
使用穩定的無障礙識別碼來定位 XCUIElement 值,然後斷言存在性、啟用/選取狀態、有意義的標籤/值,以及測試環境暴露焦點時的 hasFocus。涵蓋關閉焦點還原、每個模態退出路徑與手勢替代方案。UI 自動化是 VoiceOver、語音控制、切換控制、鍵盤、動態字型、對比、減少動態效果與減少透明度測試的補充,而非替代。XCTest 範例請載入 references/a11y-patterns.md。
常見錯誤
| 錯誤 | 修正 |
|---|---|
| 特徵指派覆寫行為 | 使用 UIKit 的 insert/remove 或 SwiftUI 的無障礙特徵修飾詞。 |
| 關閉時失去焦點 | 將無障礙焦點回到觸發元件。 |
| 列產生過多滑動停頓點 | 有意識地分組相關子元件。 |
| 標籤重複控制項類型或省略圖示意義 | 使用簡潔、可朗讀的動作/名稱;特徵會宣告類型。 |
| 動畫、文字大小、對比或透明度固定 | 回應對應的無障礙偏好設定與適應性文字樣式。 |
| 目標太小或僅有顏色 | 提供 44×44 的目標,加上文字、形狀或圖示語意。 |
| 自訂覆蓋視圖非模態 | 暴露模態語意、逃脫動作與還原。 |
審查核對清單
針對每個常見任務,記錄以下關卡的證據:
- [ ] 語意:控制項暴露簡潔的名稱、正確的角色/狀態/值,以及裝飾性、僅顏色、僅手勢或可調整內容的替代方案。
- [ ] 導覽:分組與遍歷順序有明確意圖;模態暴露模態與逃脫行為;焦點回到起始控制項。
- [ ] 輸入:語音控制名稱可朗讀且唯一,「顯示名稱」與「顯示編號」皆可運作;切換控制暴露手勢替代方案;全鍵盤操作無不可觸及的控制項、焦點陷阱或覆寫的系統快捷鍵。
- [ ] 適應性:在代表性版面與狀態中驗證動態字型、減少動態效果、減少透明度、增加對比、粗體文字與 44x44 點目標。
- [ ] 自動化:XCTest 涵蓋穩定識別碼、狀態、焦點(若可用)與每個模態退出路徑,且不取代手動輔助技術測試。
- [ ] 並行性:跨越隔離邊界的無障礙值與通知承載必須為
Sendable。 - [ ] 聲明:每個 App Store 無障礙宣告都應有針對每個宣告的裝置類型完成的常見任務證據對照表支援。
參考資料
- references/a11y-patterns.md — SwiftUI 與 UIKit 修飾詞範例、分組、自訂動作、轉輪、動態字型
- references/nutrition-labels.md — App Store 無障礙營養標籤:目前類別與通過/失敗標準
- references/media-accessibility.md — 字幕、口述影像、AVMediaCharacteristic、SDH






