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 建議
[ ] Xcode Accessibility Inspector 完整掃描
[ ] 色彩對比度檢查 (所有 UI 元素)
[ ] VoiceOver 焦點順序驗證
[ ] 實機 VoiceOver 測試 (iPad/iPhone)
[ ] 觸控目標尺寸檢查
[ ] Dynamic Type 極限測試
[ ] Dark Mode 全面驗證
[ ] 記錄發現的問題
[ ] 優先級分類 (P0/P1/P2)
[ ] 交付修正建議清單
# 方法 1: Xcode Accessibility Inspector
# Xcode → Product → Scheme → Edit Scheme → Run → Options
# 勾選 "Color Contrast"
# 執行 app,Inspector 會自動檢查對比度
# 方法 2: 線上工具
# https://webaim.org/resources/contrastchecker/
| 問題 | 原因 | 修正方案 | 優先級 |
|——|——|——–|——–|
| 灰色次要文本對比不足 | 使用 .secondary 太淺 | 改用 .secondary + 字體加粗 | P0 |
| 禁用按鈕無法區分 | 對比度 < 3:1 | 改用 opacity 降低,而非淡化色彩 | P0 |
| 深色背景上的暗色文本 | 選色不當 | 使用動態顏色集 (ColorSet) | P0 |
| 日期時間格式難以閱讀 | 字體太小 + 低對比 | 增加字體大小,提高對比度 | P1 |
Settings → Accessibility → VoiceOver → 開啟
或 使用 Command+F5 (MacBook)
Settings → Accessibility → VoiceOver → 開啟
Example: "潛水日誌,時間:2026年5月1日,位置:綠島"
accessibilityAddTraits(.isHeader)? [ ]預期順序:
1. 頁面標題 (header)
2. 搜尋/篩選欄位
3. 日誌列表項目 (由上到下)
4. 分頁標籤
5. 底部選單
accessibilityHint?
Example:
Button("刪除") { ... }
.accessibilityLabel("刪除潛水日誌")
.accessibilityHint("此操作無法復原")
Example: VStack { ... }.accessibilityElement(children: .combine)
.accessibilityAnnouncement("開始匯入檔案")
.accessibilityAnnouncement("匯入完成,共 5 個日誌")
使用實機 iPhone 或 iPad,以 VoiceOver 用戶身份測試:
// 在 SwiftUI Preview 中啟用檢查
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.sizeCategory, .extraExtraLarge) // 放大尺寸以驗證
}
}
Button("操作") { ... }
.frame(minWidth: 44, minHeight: 44) // 檢查
HStack(spacing: 12) { // 檢查是否 ≥ 8pt
Button(...) { ... }
Button(...) { ... }
}
| 位置 | 問題 | 修正 | 檢查 |
|---|---|---|---|
| 日誌列表 | 刪除按鈕太小 (16pt) | 增加到 44pt | [ ] |
| 日期選擇器 | 年月日按鈕相距太近 | 增加 spacing 到 12pt | [ ] |
| 地圖 | 縮放按鈕 (+/-) 太小 | 使用預設 MapKit 按鈕 | [ ] |
| 設定 | 語言選擇單選按鈕太小 | 使用 Toggle (預設 44pt) | [ ] |
Settings → Accessibility → Display & Text Size
測試尺寸: xSmall, Small, Medium, Large, extraLarge, xxxLarge
.font(.body) // 自動按 Dynamic Type 調整
.font(.title) // 自動按 Dynamic Type 調整
// ❌ 硬編碼
.font(.system(size: 17))
// ✅ 動態
.font(.body) // 或 .system(size: 17).scaledFont(for: .body)
Control Center (Cmd+Shift+D) → 長按亮度 → Dark Mode
或 Settings → Display → Dark
Settings → Display & Brightness → Dark
| 問題 | 原因 | 修正 | 檢查 |
|---|---|---|---|
| 文本在 Dark 中看不見 | 使用白色文本 + 白色背景 | 使用 ColorSet | [ ] |
| 圖標反轉 | 沒有指定 tint 顏色 | 明確設定 .foregroundColor |
[ ] |
| 地圖太暗 | 背景層顏色衝突 | 使用 mapStyle API | [ ] |
本節原始框架保留(見下方合規宣告),實際簽核依據為附錄 1–6 的真機/模擬器 實測結果,非本節逐項填寫,見上方 2026-07-28 現況說明。
☑ 本應用程式符合 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
以 Xcode Accessibility Inspector 對 iOS / macOS 實機稽核,並完成下列修正。
.onTapGesture 互動,已補 .accessibilityAddTraits(.isButton) + .accessibilityAction(16 → ~0)。.tertiary 文字改 .secondary;空狀態裝飾圖示標 .accessibilityHidden(true)。failed 29 → 個位數、nearly 71 → ~30。Color.accessibleSecondary(達標灰):取代 StatsHeader / KeyStatCell / DetailRow / SheetStatCell 的次要文字 .secondary,淺色 white 0.32、深色 white 0.75,與背景實際對比 ≥ 4.5:1(白卡上約 8:1)。.accessibilityElement(.ignore) + 以格式名稱為 label(解「.csv 被讀成『點 c s v』」非人類可讀)。location.fill 等標 .accessibilityHidden(true)。adjustsFontForContentSizeCategory)。App 實際符合 Dynamic Type。.accessibilityElement(children:.combine) 節點(DetailRow / StatsHeader / hero header):標籤已用達標灰(白卡 ~8:1),Inspector 仍判 failed → 工具無法正確讀取 combine 合併節點內各段文字顏色,屬同類誤報。實際渲染對比達標。ContentUnavailableView 空狀態文字,由系統繪製,可調空間有限。NSHostingView、NavigationSplitCore、_SystemTextFieldCell、視窗紅綠燈 NSThemeWidgetZoomMenuRemoteView、AccessibilityLazyLayoutNode 等為 SwiftUI/AppKit 宿主層,非 App 程式,無法修。以「實機 VoiceOver 走查」+「真實渲染色的對比計算」為準,而非追 Accessibility Inspector 的絕對數字(含上述誤報)。
背景:v1.2 送審前複查 4 項未驗證清單。先用程式碼審查核對 2026-06-01 修復是否仍有效、 之後新增程式碼有沒有引入新違規;後續追加用 iPhone 17 Simulator 實際跑起來測(build
- launch +
xcrun simctl ui content_size切換字級 + 截圖比對),Dynamic Type 這項純 程式碼審查漏掉一個真違規,模擬器實測才抓到——記錄下來提醒之後任何「只審程式碼就打勾」 的判斷都要小心,能跑模擬器就應該跑。仍不能取代實機 VoiceOver 完整走查(模擬器 VoiceOver 手勢語意跟真機不完全一樣,這次沒測 VoiceOver 朗讀)。
DiveCalendarView.swift 年份 stepper 的 ‹/› chevron 按鈕:原本 .frame(width: 32, height: 32)
且無 .contentShape 擴大,實際可點擊區域只有 32×32pt,低於 44×44pt 標準。已改為
.frame(width: 44, height: 44) + .contentShape(Rectangle())。.frame(width:/height: <44) 的地方(DiveAnalysisView 警示圓點、
ImportWizardView 步驟圓圈、Divider、DetailRow 圖示欄寬等):確認皆為裝飾性/非互動元素,
不是使用者要點的目標,不需要修。MainTabView.swift 3 處 macOS 專用工具列按鈕(空狀態「+」、split view 的「+」與
清單/月曆切換)只有 .help()(滑鼠 tooltip),沒有 .accessibilityLabel()——
.help() 不等於 VoiceOver label,兩者要同時存在。已補上對應 .accessibilityLabel()。.accessibilityLabel();.onTapGesture 手勢互動(列表卡片、月曆日期格)
也已有對應 .accessibilityAddTraits(.isButton) + .accessibilityAction(2026-06-01 已修)。Color.accessibleSecondary(Views/Shared/Color+Platform.swift);
v1.2 新增的配重/氣瓶壓力欄位走既有 DetailRow 元件(自動套用 accessibleSecondary)或
系統 Form/Section footer 預設樣式(系統自行保證對比),未發現繞過既有機制、自己硬編碼
顏色的新增程式碼。grep ".system(size:" 找到的 6 處全部是 Image(systemName:)
裝飾性圖示,沒有任何文字用硬編碼字級,看起來沒問題——但這個結論是不完整的。xcrun simctl ui <udid> content_size accessibility-extra-extra-extra-large
DiveKitUI.DiveStatCell(.hero/.compact 兩種 style)內建的 minimumScaleFactor
只保證單一欄位文字不溢出自己的框,擋不住 3 欄硬擠一列造成的整體重疊:DiveLogListView.swift StatsHeaderView(日誌列表頁「105 / 82h9m / 40.6m」統計列)DiveLogDetailView.swift keyStatsRow(詳情頁「主要數據」深度/時間/水溫)DiveSiteSheetView.swift keyStatsRow(地圖潛點卡片,.compact style 連
minimumScaleFactor 都沒有,理論上更容易溢出)
DiveStatCell 本體是 _JD2-family/DiveKit 的 DiveKitUI 共用元件,不在本 App 改;
修法是 App 層加 @Environment(\.dynamicTypeSize),isAccessibilitySize 為真時這 3 處
呼叫端各自改成直式 VStack 排列(元件本身不動,只改 App 層怎麼排列元件)。3 處皆用
模擬器截圖覆測確認直式排列後不再重疊。色彩對比/VoiceOver Label/觸控目標仍主要靠程式碼審查(未逐一在模擬器裡用滑鼠模擬點擊 驗證每個熱區、未做真實像素取色量對比值);模擬器沒有跑真機 VoiceOver 朗讀走查(本檔 第 2.6 節四個工作流),VoiceOver 手勢在模擬器跟真機語意不完全一樣,這項仍待真機執行。 Dynamic Type 這次雖然用模擬器實測到 AX5,但沒有測完整頁面(部分頁面模擬器手勢滾動不 穩定,只驗證到卡片本身不重疊,未逐頁滾到底)。
第 2.6 節是專案初期寫的通用範本,流程跟現在的 App 已經對不上(例如「新增日誌」現在 有手動輸入跟匯入精靈兩條路,不是只有匯入)。這份改成針對這次真的動過的程式碼排 優先序,PM 真機測的時候直接照這個順序做,不用管第 2.6 節。
‹/›(這次剛把熱區從 32pt 改成 44pt):點兩下應該正常換年,
VoiceOver 念出「上一年,按鈕」/「下一年,按鈕」測到「聽起來不對」的地方,麻煩記兩件事回報給我:
我可以直接對照到程式碼裡哪個 .accessibilityLabel/.accessibilityHint 出問題,不需要
你自己判斷是哪段程式碼。
依附錄 3 腳本實測回報 4 個具體問題+2 個較大範圍的問題,逐項處理結果:
DetailRow(詳情頁「潛水資訊」區塊)VoiceOver 唸英文,畫面顯示中文正確
—— .accessibilityLabel("\(label): \(value)") 把呼叫端傳進來的英文 key
("Gas"/"Environment"/"Average Depth"/"Source Format" 等)直接內插進
朗讀字串,沒有經過本地化;畫面上的 Text(LocalizedStringKey(label)) 是另一條路,
有走本地化所以顯示正確,兩條路沒對齊。改成
.accessibilityLabel("\(languageManager.localized(label)): \(value)")。
已確認同款元件 DiveSiteSheetView.SheetDetailRow 沒有這個問題(呼叫端本來就是
傳已解析好的字串進去,不是傳 key)。DatePicker VoiceOver 朗讀日期是英文,畫面顯示中文正確
—— 這次不是我們的程式碼,是 SwiftUI DatePicker 內部朗讀值的生成不吃
\.environment(\.locale)(Apple 元件行為,同一類病灶換一個地方發作)。用
.accessibilityValue(languageManager.numericDateTimeFormatter().string(from: entryTime))
直接蓋掉系統算出來的朗讀值。maxDepth == 0 時
isSaveEnabled 正確地擋住存檔,但畫面上完全沒有任何提示(欄位只是把
placeholder 淡化成灰色),VoiceOver 使用者更不可能靠肉眼掃描發現。已加:
Section footer 可見紅字提示、Max Depth 欄位 .accessibilityLabel 區分「必填未填」
vs「已填 0」、Save 鈕停用時的 .accessibilityHint 直接講原因。git log 追出是 2026-07-26 一次「縮短過長
翻譯」的批次 commit(9eb43e33,跟本次會話較早的「Ceiling→Decotiefe」同一類
縮短動作)誤傷:日文/越南文那批縮短是忠實保留原意(拿掉「圖/表」字但保留
「Profile」語意),中文那批卻把「剖面圖」整個換成語意不同的「曲線」,不是單純
縮短,是誤譯。已改回「潛水剖面圖」/「潜水剖面图」。String(localized:
確認沒有殘留呼叫(本次會話已修完的既有病灶都還在修復狀態,沒有退化)。研判
兩個可能來源:(a) 上面第 1 項 DetailRow 的英文朗讀,本身就會被誤認成「殘留」
(因為不管切到哪個語言那幾欄永遠是英文,感覺很像「沒切乾淨」)——這部分現在
已修,麻煩這一版重新測一次看殘留感是否消失;(b) macOS 原生選單列(App
名稱選單/Edit/Window 等系統標準選單項目)是 AppKit 在啟動時就建好、綁定
系統語言,不是我們 Localizable.xcstrings 管得到的範圍,AppLanguageManager.swift
檔頭本來就寫明「行程級 override 下次啟動才 100% 生效」——這塊是已知、需要重開
App 才會完全生效的限制,不是這次新退化。如果重測後 (a) 的部分改善了但還有
殘留,麻煩具體說是哪個畫面/元素,才能判斷是不是踩到 (b) 這類系統選單限制。DiveMapRepresentable.swift/DiveSiteAnnotation.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 點覆測)。
依附錄 3 腳本、對照第 2.6 節四個工作流,PM 在真機上完整覆測:
DetailRow 不再唸
英文、Entry Time 不再唸英文、Save 鍵停用時有星號+「必填」提示、「潛水剖面圖」
唸法恢復正確)DetailRow 英文殘留造成的錯覺,隨那項
修復一併解決)四個工作流裡三個全過,唯一剩下的已知問題是地圖。這不是「還沒修好的 bug」, 是「需要另外規劃的功能缺口」——讓地圖在 VoiceOver 下完全可操作通常需要:
這三項合起來是一個範圍明確、工程量中等的獨立功能,不是這次能一併小修解決的。
建議:v1.2 這次先在 V1_RELEASE_CHECKLIST.md/docs/KNOWN_ISSUES.md 記錄為
已知限制(日誌列表提供可行的替代瀏覽路徑),排入下一版 backlog 再規劃地圖無障礙
改造,不要為了趕這次上架臨時湊一版沒驗證過的方案。
PM 問「WCAG 其他部分你可以用模擬機測完嗎?」,答案分兩半:
Dark Mode + 色彩對比:xcrun simctl ui <udid> appearance dark 切換外觀,
xcrun simctl io <udid> screenshot <path> 存實際像素的 PNG(不是 MCP 工具回傳的
壓縮預覽圖),寫 Python/PIL 腳本直接套 WCAG 相對亮度公式算真實對比值,不是憑
螢幕截圖用眼睛判斷。掃過日誌列表、詳情頁,抓到 1 個真違規並修復:
DiveRowView.dateBlock 的月/年標籤(列表卡片左側「7月/2026」)在 Dark Mode 用
系統 .secondary,實測 3.91:1(低於 4.5:1 一般文字門檻,屬於 caption 級小字,
不適用 3:1 大字級門檻)。這正是 2026-06-01 稽核修 DetailRow/StatsHeader/
KeyStatCell/SheetStatCell 時同一個病灶,只是那輪漏掉了這個元件。改用
Color.accessibleSecondary,複測 9.25:1,同一行的水溫標籤有一併複查
(5.20:1 本來就過,沒有動——不是每處 .secondary 都有問題,逐一實測才知道)。accessibleSecondary 9.25:1、
主要文字(白/接近白)17:1、Section header 6.36:1、圖示藍色 5.26:1,全數通過。Safe Area / Dynamic Island:iPhone 17 模擬器截圖確認頂部內容(時間、電池、 Dynamic Island)跟下方工具列有正常留白,沒有內容被遮擋。
xcrun simctl 開啟輔助功能設定,也讀不出「螢幕朗讀了什麼」這件事本身,只能
用程式碼判斷「理論上應該要念什麼」,這正是這次抓到好幾個回歸(DetailRow 唸
英文、DatePicker 唸英文)的原因——程式碼看起來沒問題,只有真的聽了才知道錯。
這部分永遠需要真機。結論:色彩對比/Dynamic Type/Dark Mode 這幾項已經用模擬器盡可能做到「實測有 數據,不是猜」;VoiceOver 朗讀內容跟真實觸控手感,結構上就是模擬器做不到的事, 只能靠真機。