JoyDive

WCAG 2.1 AA 審核執行檢查清單

Week 12 最終合規驗證 (1 小時 PM 投入)

2026-07-28 現況說明:本檔第 1–12 節是專案初期撰寫的 Week 12(原訂 2026 年 8 月 2–9 日)審核規劃範本,當時所有檢查項皆為空白待填。實際 執行時程比原計畫提前:因 v1.2 送審前需要真機驗證,審核工作已提前於 2026-06-01、2026-07-26、2026-07-27 分三輪實際完成(見下方附錄 1–6,含 Accessibility Inspector 實測、程式碼複查、模擬器截圖取真實像素 算對比值、真機 VoiceOver 完整走查與 PM 回報結果)。附錄才是實際稽核結果 的權威來源;第 1–12 節的空白檢查框保留為原始規劃範本,不逐項回填(部分 逐項填寫等同編造未實際執行的驗證細節,故意留白比造假更誠實)。

送審現況:iOS/macOS v1.2 (Build 3) 已於 2026-07-28 送出審核(早於本檔原訂 Week 12 窗口),詳見 V1_2_BACKLOG.md #1。

審核日期: 2026 年 8 月 2-9 日 (Week 12,原始規劃;實際提前完成見上方說明) 合規目標: WCAG 2.1 Level AA(最佳實踐目標,非已知強制法規——2026-07-27 查證: Apple 審核本身不會因無障礙缺陷退件(僅 iOS 18+ 有開發者自行勾選的 Accessibility Nutrition Label,非審核關卡);EU EAA 雖有實質罰則但對 10 人以下/年營收 200 萬歐元 以下的小型團隊有豁免,本專案規模應符合豁免;原先寫的「美國法規要求 2026-2027」 查無對應到私人商用 App 的美國聯邦法規,最接近的 DOJ ADA Title II 2026/2027 期限 規範對象是州/地方政府數位內容,不適用本 App,此行主張已移除)
預計時間: 1 小時 PM 工作 + Claude 建議


審核進度追蹤

Phase 1: 自動化檢查 (30 分鐘)

[ ] Xcode Accessibility Inspector 完整掃描
[ ] 色彩對比度檢查 (所有 UI 元素)
[ ] VoiceOver 焦點順序驗證

Phase 2: 手動驗證 (25 分鐘)

[ ] 實機 VoiceOver 測試 (iPad/iPhone)
[ ] 觸控目標尺寸檢查
[ ] Dynamic Type 極限測試
[ ] Dark Mode 全面驗證

Phase 3: 修正與報告 (5 分鐘)

[ ] 記錄發現的問題
[ ] 優先級分類 (P0/P1/P2)
[ ] 交付修正建議清單

1. 色彩對比度審核

工具與方法

自動化檢查

# 方法 1: Xcode Accessibility Inspector
# Xcode → Product → Scheme → Edit Scheme → Run → Options
# 勾選 "Color Contrast"
# 執行 app,Inspector 會自動檢查對比度

# 方法 2: 線上工具
# https://webaim.org/resources/contrastchecker/

手動檢查 (使用 WebAIM 工具)

  1. 在 Xcode 中截圖每個 UI 狀態
  2. 記錄文本顏色與背景顏色的 RGB 值
  3. 輸入 WebAIM 工具,確認對比度

檢查清單

正常文本 (Normal Text)

大文本 (Large Text) - 18pt+ 或 14pt+ Bold

邊框/分隔線

圖標與圖形

Light Mode vs Dark Mode

常見問題與修正

| 問題 | 原因 | 修正方案 | 優先級 | |——|——|——–|——–| | 灰色次要文本對比不足 | 使用 .secondary 太淺 | 改用 .secondary + 字體加粗 | P0 | | 禁用按鈕無法區分 | 對比度 < 3:1 | 改用 opacity 降低,而非淡化色彩 | P0 | | 深色背景上的暗色文本 | 選色不當 | 使用動態顏色集 (ColorSet) | P0 | | 日期時間格式難以閱讀 | 字體太小 + 低對比 | 增加字體大小,提高對比度 | P1 |


2. VoiceOver 可達性審核

啟用 VoiceOver

iOS 模擬器

Settings → Accessibility → VoiceOver → 開啟
或 使用 Command+F5 (MacBook)

實機 (推薦)

Settings → Accessibility → VoiceOver → 開啟

審核清單

2.1 標籤 (Labels)

2.2 焦點順序 (Focus Order)

預期順序:
1. 頁面標題 (header)
2. 搜尋/篩選欄位
3. 日誌列表項目 (由上到下)
4. 分頁標籤
5. 底部選單

2.3 提示 (Hints)

2.4 群組與容器

2.5 狀態變化通知

2.6 實機 VoiceOver 完整工作流測試

使用實機 iPhone 或 iPad,以 VoiceOver 用戶身份測試:


3. 觸控目標尺寸審核

檢查方法

使用 Xcode Accessibility Inspector

// 在 SwiftUI Preview 中啟用檢查
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environment(\.sizeCategory, .extraExtraLarge)  // 放大尺寸以驗證
    }
}

手動測試

常見問題

位置 問題 修正 檢查
日誌列表 刪除按鈕太小 (16pt) 增加到 44pt [ ]
日期選擇器 年月日按鈕相距太近 增加 spacing 到 12pt [ ]
地圖 縮放按鈕 (+/-) 太小 使用預設 MapKit 按鈕 [ ]
設定 語言選擇單選按鈕太小 使用 Toggle (預設 44pt) [ ]

4. Dynamic Type 支援審核

測試步驟

Simulator 設定

Settings → Accessibility → Display & Text Size
測試尺寸: xSmall, Small, Medium, Large, extraLarge, xxxLarge

檢查項目

驗證清單


5. Dark Mode 審核

啟用 Dark Mode

Simulator

Control Center (Cmd+Shift+D) → 長按亮度 → Dark Mode
或 Settings → Display → Dark

實機

Settings → Display & Brightness → Dark

檢查清單

常見問題

問題 原因 修正 檢查
文本在 Dark 中看不見 使用白色文本 + 白色背景 使用 ColorSet [ ]
圖標反轉 沒有指定 tint 顏色 明確設定 .foregroundColor [ ]
地圖太暗 背景層顏色衝突 使用 mapStyle API [ ]

6. 多語言本地化審核

語言切換測試

字符串完整性檢查


7. Safe Area & Dynamic Island

iOS 18 特定檢查


8. 鍵盤與文字輸入


9. 性能與穩定性

記憶體與 CPU

崩潰與錯誤


10. 綜合測試 (最後驗證)

端到端工作流


11. 問題記錄與優先級

P0 (必須修正,App 無法上線)

P1 (應該修正,可延至 v1.0.1)

P2 (可優化,不影響上線)


12. 最終簽核

本節原始框架保留(見下方合規宣告),實際簽核依據為附錄 1–6 的真機/模擬器 實測結果,非本節逐項填寫,見上方 2026-07-28 現況說明。

審核完成確認(依附錄 5 PM 真機覆測結果,2026-07-27)

合規宣告

☑ 本應用程式符合 WCAG 2.1 Level AA 標準(地圖 VoiceOver 互動為已知限制,見附錄 5)
☐ 所有 iOS 18 新功能已正確整合(已終止規劃,不適用,見 UI_UX_SPEC.md §9)
☑ 多語言本地化完整
☑ 性能與穩定性符合預期
☑ 準備提交 App Store 審核(已於 2026-07-28 送出,見 V1_2_BACKLOG.md #1)

審核備註

唯一未通過項目:地圖頁在 VoiceOver 下無法縮放/平移/展開聚合/選其他 pin,
確認為 MapKit 手勢與 VoiceOver 手勢接管的固有限制,非本專案程式碼退化。
日誌列表提供功能對等的替代瀏覽路徑(完整 VoiceOver 可操作)。已記錄於
docs/KNOWN_ISSUES.md,地圖無障礙改造(獨立縮放按鈕/逐一切換 pin)排入
下一版 backlog,不影響本次送審。

下一步:


實際審核完成日期:2026-07-27(附錄 5 真機覆測完成) 實際送審日期:2026-07-28(見 V1_2_BACKLOG.md #1) 原始規劃日期(已提前,僅供追溯):審核完成 2026-08-09/提審 2026-08-11/上線 2026-08-18


附錄:Accessibility Inspector 實測結果與已知限制(2026-06-01)

以 Xcode Accessibility Inspector 對 iOS / macOS 實機稽核,並完成下列修正。

已修正

已知限制(工具誤報 / 系統元件,不修)

建議驗收方式

以「實機 VoiceOver 走查」+「真實渲染色的對比計算」為準,而非追 Accessibility Inspector 的絕對數字(含上述誤報)。


附錄 2:程式碼複查+模擬器實測(2026-07-26)

背景:v1.2 送審前複查 4 項未驗證清單。先用程式碼審查核對 2026-06-01 修復是否仍有效、 之後新增程式碼有沒有引入新違規;後續追加用 iPhone 17 Simulator 實際跑起來測(build

觸控目標(新發現並修復)

VoiceOver Label(新發現並修復)

色彩對比(複查,無新發現)

Dynamic Type(程式碼複查沒抓到,模擬器實測抓到,已修復)

本輪限制

色彩對比/VoiceOver Label/觸控目標仍主要靠程式碼審查(未逐一在模擬器裡用滑鼠模擬點擊 驗證每個熱區、未做真實像素取色量對比值);模擬器沒有跑真機 VoiceOver 朗讀走查(本檔 第 2.6 節四個工作流),VoiceOver 手勢在模擬器跟真機語意不完全一樣,這項仍待真機執行。 Dynamic Type 這次雖然用模擬器實測到 AX5,但沒有測完整頁面(部分頁面模擬器手勢滾動不 穩定,只驗證到卡片本身不重疊,未逐頁滾到底)。


附錄 3:真機 VoiceOver 走查腳本(2026-07-26,取代第 2.6 節舊版)

第 2.6 節是專案初期寫的通用範本,流程跟現在的 App 已經對不上(例如「新增日誌」現在 有手動輸入跟匯入精靈兩條路,不是只有匯入)。這份改成針對這次真的動過的程式碼排 優先序,PM 真機測的時候直接照這個順序做,不用管第 2.6 節。

前置設定

  1. 設定 → 輔助使用 → VoiceOver → 開啟(或先到「輔助使用捷徑」勾 VoiceOver,之後 三下側邊鍵快速開關,測完記得關掉,不然沒法正常滑動用 App)
  2. 手勢複習(不熟 VoiceOver 的人容易卡在這步):
    • 單指點一下=選取/朗讀該元素(不會觸發動作)
    • 雙指點兩下=等於平常的「點一下」,真正觸發按鈕/連結
    • 單指左右滑=移到上一個/下一個元素
    • 單指上下滑=依元素類型調整(通常沒用,用左右滑就好)
    • 三指上下滑=捲動畫面
    • 雙指在螢幕畫 Z=返回上一頁(等於 Back)

第一優先:這次改過/新發現有動的地方(最可能有回歸)

  1. 新增潛水(手動輸入,不是匯入)
    • 日誌頁右上「+」→ 應該聽到類似「新增,按鈕」
    • 依序滑過表單每個欄位:配重/初壓/終壓現在允許空白顯示「–」,VoiceOver 應該念出 類似「配重,未記錄」而不是念出裸的「–」符號或完全跳過不念
    • 找一筆 Trimix 潛水點「編輯」:Gas Mix 的 Air/Nitrox 切換鈕應該被識別為「已停用」, 且下方要有一段說明文字被念出來(不能是被停用的按鈕卻沒有任何解釋,使用者會以為 App 壞掉)
    • 判斷標準:VoiceOver 使用者光憑耳朵,能不能知道「這格沒填」「這格為什麼點不動」, 不能只是沉默或念一個看不懂的符號
  2. 潛水詳情頁「主要數據」卡片(這次因為 Dynamic Type 重疊改成一般字級橫排/ accessibility 字級直式排列兩種版面)
    • 一般字級:滑過深度/時間/水溫三格,順序應該是「最大深度 15.2 公尺」→ 「潛水時間 42 分鐘」→「水溫 24 度」,不能把數字跟標籤拆開念、也不能三格黏在一起 變成一長串念不清楚
    • 到「設定 → 輔助使用 → 顯示與文字大小 → 更大的文字」拉到最大,回到同一頁再測一次 ——兩種版面(橫排/直排)朗讀順序跟內容都要一致,不能因為排版換成直式,順序就 變亂或漏念某一格
    • 判斷標準:換版面不該換朗讀邏輯,這是新增的分支邏輯要重點驗證的地方
  3. 月曆頁
    • 年份切換的 /(這次剛把熱區從 32pt 改成 44pt):點兩下應該正常換年, VoiceOver 念出「上一年,按鈕」/「下一年,按鈕」
    • 有潛水記錄的日期格:應該念出日期+「有潛水記錄」;沒有記錄的只念日期
    • 月份格子(12 宮格快速跳選):滑過去要能聽到月份名稱,不是只有數字
  4. macOS 版(如果有 Mac 可測;這次修的是純 VoiceOver label,沒有實際功能變化, 優先度較低,iOS 測完有餘力再測)
    • 開啟 App 用旁白(macOS 是 Cmd+F5 或系統設定開 VoiceOver),Tab 到工具列的「+」 跟清單/月曆切換按鈕,應該念出完整描述而不是只念「按鈕」兩個字

第二優先:一般工作流(多半是既有功能,抽測即可,不用每個都做)

  1. 日誌列表:上下滑動應該逐項念出「日期、地點、深度、時間」組合成一句話,不是 分開念四次
  2. 刪除潛水:詳情頁垃圾桶圖示要念出「刪除潛水,按鈕」,點兩下跳出確認對話框, 對話框裡的按鈕文字要能正常朗讀(尤其「無法復原」這類警示文字)
  3. 地圖潛點卡片:點地圖 pin 跳出的卡片,關閉按鈕(X)要念得出來,卡片內三欄 統計(跟第 2 項同款元件,一樣要測橫排/直排兩種版面)
  4. 語言切換:設定頁切換語言後,不要重開 App,直接用 VoiceOver 滑回日誌頁, 確認朗讀的語言也跟著換了(這是驗證「語言切換即時生效」這條線,不是只驗證畫面文字 換了,是連 VoiceOver 念的內容都要換)

回報方式

測到「聽起來不對」的地方,麻煩記兩件事回報給我:

  1. 在哪一頁、哪個元素(例如「詳情頁的刪除按鈕」)
  2. VoiceOver 實際念了什麼(不用逐字準確,大概意思即可,例如「只念了『按鈕』兩個字, 沒有說是刪除」)

我可以直接對照到程式碼裡哪個 .accessibilityLabel.accessibilityHint 出問題,不需要 你自己判斷是哪段程式碼。


附錄 4:第一輪真機 VoiceOver 回報(2026-07-27)

依附錄 3 腳本實測回報 4 個具體問題+2 個較大範圍的問題,逐項處理結果:

已修復(程式碼層級,逐一定位到根因)

  1. DetailRow(詳情頁「潛水資訊」區塊)VoiceOver 唸英文,畫面顯示中文正確 —— .accessibilityLabel("\(label): \(value)") 把呼叫端傳進來的英文 key ("Gas""Environment""Average Depth""Source Format" 等)直接內插進 朗讀字串,沒有經過本地化;畫面上的 Text(LocalizedStringKey(label)) 是另一條路, 有走本地化所以顯示正確,兩條路沒對齊。改成 .accessibilityLabel("\(languageManager.localized(label)): \(value)")。 已確認同款元件 DiveSiteSheetView.SheetDetailRow 沒有這個問題(呼叫端本來就是 傳已解析好的字串進去,不是傳 key)。
  2. Entry Time 的 DatePicker VoiceOver 朗讀日期是英文,畫面顯示中文正確 —— 這次不是我們的程式碼,是 SwiftUI DatePicker 內部朗讀值的生成不吃 \.environment(\.locale)(Apple 元件行為,同一類病灶換一個地方發作)。用 .accessibilityValue(languageManager.numericDateTimeFormatter().string(from: entryTime)) 直接蓋掉系統算出來的朗讀值。
  3. 新增潛水「無法儲存」——查證後不是存檔邏輯壞掉,是 maxDepth == 0isSaveEnabled 正確地擋住存檔,但畫面上完全沒有任何提示(欄位只是把 placeholder 淡化成灰色),VoiceOver 使用者更不可能靠肉眼掃描發現。已加: Section footer 可見紅字提示、Max Depth 欄位 .accessibilityLabel 區分「必填未填」 vs「已填 0」、Save 鈕停用時的 .accessibilityHint 直接講原因。
  4. 「潛水剖面圖」變成「潛水曲線」——git log 追出是 2026-07-26 一次「縮短過長 翻譯」的批次 commit(9eb43e33,跟本次會話較早的「Ceiling→Decotiefe」同一類 縮短動作)誤傷:日文/越南文那批縮短是忠實保留原意(拿掉「圖/表」字但保留 「Profile」語意),中文那批卻把「剖面圖」整個換成語意不同的「曲線」,不是單純 縮短,是誤譯。已改回「潛水剖面圖」/「潜水剖面图」。

已調查、非新增程式碼問題,如實記錄限制

  1. 語言切換後 VoiceOver 語音殘留,macOS 版嚴重——全專案 grep String(localized: 確認沒有殘留呼叫(本次會話已修完的既有病灶都還在修復狀態,沒有退化)。研判 兩個可能來源:(a) 上面第 1 項 DetailRow 的英文朗讀,本身就會被誤認成「殘留」 (因為不管切到哪個語言那幾欄永遠是英文,感覺很像「沒切乾淨」)——這部分現在 已修,麻煩這一版重新測一次看殘留感是否消失;(b) macOS 原生選單列(App 名稱選單/Edit/Window 等系統標準選單項目)是 AppKit 在啟動時就建好、綁定 系統語言,不是我們 Localizable.xcstrings 管得到的範圍,AppLanguageManager.swift 檔頭本來就寫明「行程級 override 下次啟動才 100% 生效」——這塊是已知、需要重開 App 才會完全生效的限制,不是這次新退化。如果重測後 (a) 的部分改善了但還有 殘留,麻煩具體說是哪個畫面/元素,才能判斷是不是踩到 (b) 這類系統選單限制
  2. 地圖 VoiceOver 下無法縮放/平移/展開聚合/選其他 pin——查過 DiveMapRepresentable.swiftDiveSiteAnnotation.swift:沒有設 isZoomEnabled/isScrollEnabled 為 false、沒有自訂手勢蓋掉系統手勢、annotation view 的 title/subtitle 也都有正常賦值(VoiceOver 預設會唸這兩個欄位)。程式碼 層面沒找到明顯的退化或誤設定。這比較可能是 MapKit 本身在 VoiceOver 下的 固有限制——VoiceOver 開啟時單指/雙指手勢整套被 VoiceOver 自己接管(單指滑動 變成「移到下一個元素」而非平移地圖),MapKit 原生對此有自己的 adjustable trait/手勢支援,但這塊沒辦法透過模擬器驗證(模擬器沒有真正的 VoiceOver 手勢語意),也沒有真機可以測,所以沒有動這部分的程式碼,避免盲改。 建議:短期把「日誌列表」(已完全 VoiceOver 可操作,用滑動就能瀏覽所有潛點) 當作視障使用者瀏覽潛點的替代路徑;如果要讓地圖本身也完全 VoiceOver 可操作 (獨立的縮放按鈕、逐一切換 pin 的控制項),這是一項範圍明確的新功能,不是 小修能解決,需要另外排時間做,不建議現在倉促猜著改。

驗證

iOS + macOS 皆重新 build 過(xcodebuild/simctl headless build),clean。第 1-4 項 已在 iPhone 17 Simulator 上重新走過一次畫面確認沒有編譯期/明顯執行期錯誤,但 這次沒有用模擬器逐一重播 VoiceOver 朗讀內容(模擬器上這麼做的訊噪比不好, 下一輪建議直接在真機上照著上面 4 點覆測)。


附錄 5:真機 VoiceOver 覆測結果(2026-07-27,PM 執行)

依附錄 3 腳本、對照第 2.6 節四個工作流,PM 在真機上完整覆測:

現況與後續方向

四個工作流裡三個全過,唯一剩下的已知問題是地圖。這不是「還沒修好的 bug」, 是「需要另外規劃的功能缺口」——讓地圖在 VoiceOver 下完全可操作通常需要:

  1. 獨立的「放大」/「縮小」按鈕(VoiceOver 使用者無法用兩指縮放,因為那個手勢 被 VoiceOver 自己接管了)
  2. 逐一切換 pin 的機制(例如「下一個潛點」/「上一個潛點」按鈕,取代靠手指在 地圖上直接點選)
  3. 聚合展開的替代操作(不依賴雙擊聚合徽章)

這三項合起來是一個範圍明確、工程量中等的獨立功能,不是這次能一併小修解決的。 建議:v1.2 這次先在 V1_RELEASE_CHECKLIST.mddocs/KNOWN_ISSUES.md 記錄為 已知限制(日誌列表提供可行的替代瀏覽路徑),排入下一版 backlog 再規劃地圖無障礙 改造,不要為了趕這次上架臨時湊一版沒驗證過的方案。


附錄 6:模擬器可測 vs 只能真機測(2026-07-27)

PM 問「WCAG 其他部分你可以用模擬機測完嗎?」,答案分兩半:

這次用模擬器實測完成(非目測,非程式碼審查)

Dark Mode + 色彩對比xcrun simctl ui <udid> appearance dark 切換外觀, xcrun simctl io <udid> screenshot <path> 存實際像素的 PNG(不是 MCP 工具回傳的 壓縮預覽圖),寫 Python/PIL 腳本直接套 WCAG 相對亮度公式算真實對比值,不是憑 螢幕截圖用眼睛判斷。掃過日誌列表、詳情頁,抓到 1 個真違規並修復:

Safe Area / Dynamic Island:iPhone 17 模擬器截圖確認頂部內容(時間、電池、 Dynamic Island)跟下方工具列有正常留白,沒有內容被遮擋。

這次仍然無法用模擬器測(結構性限制,不是偷懶)

結論:色彩對比/Dynamic Type/Dark Mode 這幾項已經用模擬器盡可能做到「實測有 數據,不是猜」;VoiceOver 朗讀內容跟真實觸控手感,結構上就是模擬器做不到的事, 只能靠真機。