回部落格
iThome 鐵人賽Vibe CodingSwiftUIiOS 26XCUITestSpectra

iThome 鐵人賽 Day 16:iOS 26 confirmationDialog 行為變更,從來源按鈕彈出,XCUITest 要跟著調整

SwiftUI 的 confirmationDialog 一行程式都沒改,到了 iOS 26 卻改從觸發它的按鈕彈出。這篇記錄修卡片編輯與刪除時遇到的呈現變更、UI 測試為什麼找不到按鈕,以及先寫規格再讓 AI 實作時,spectra analyze 抓出的漏洞。

Steven Chang
Steven Chang
2026年9月30日 01 Min read
訂閱
iThome 鐵人賽 Day 16:iOS 26 confirmationDialog 行為變更,從來源按鈕彈出,XCUITest 要跟著調整

本文同步發表於 iThome 鐵人賽,系列:神隊友.swift:30 天 Vibe Coding 打造育兒 iOS App

SwiftUI 的 confirmationDialog 一行程式都沒改,到了 iOS 26,出現的位置卻變了。

Day 15 預告今天要裝到兩支 iPhone。裝機前,先把模擬器上用起來不順的地方調整好:卡片可以編輯和刪除,首頁多了一區「之後」,7 天以後的事也看得到。裝機改到 Day 18。

首頁新增的「之後」區:7 天以後的事也看得到,一樣標出對方知道了沒有

先講結果:兩份規格共 14 項任務全部完成,App 測試從 122 個增加到 142 個,verify.sh 通過。

今天的技術主軸在 Day 7 學習地圖的第二層 SwiftUI:iOS 26 的確認對話框換了呈現方式,畫面和 UI 測試都要跟著調整。

1. 規格先寫,AI 再動手

Day 7 定下「Spectra 寫規格、Claude Code 實作」的分工,Day 8 在第一版的 repo 初始化過 Spectra;這次在 v2 的 repo 也建立起來:

spectra init --tools claude          # openspec/ + Claude Code skills
spectra new change <change-name>     # one folder per change
spectra analyze <change-name>        # consistency check
spectra validate <change-name>       # format check

一個 change 由四份文件組成,照順序寫:

文件寫什麼不寫什麼
proposal為什麼要改、改哪些、不做哪些實作細節
spec每一條需求和情境(WHEN/THEN)怎麼寫程式
design技術上怎麼做、為什麼選這個做法行號、逐行步驟
tasks可以打勾的任務,每項寫明怎麼驗證「修改某檔案」這種沒有驗收的任務

每一條情境都給一個驗收編號,之後測試名稱直接帶這個編號,格式是 test_AC_<功能>_<編號>_<情境>。規格裡的情境和測試一對一,AI 做完有沒有漏,對編號就知道。

寫規格的過程中,Claude Code 會把需求沒講清楚的地方整理成選擇題問我。今天我總共回答了七題,都是「誰可以做」「做了之後另一半那邊會發生什麼事」這一類;這些 AI 不能自己假設,專案規則寫明有疑義就要停下來問。

2. 規格要對到程式碼,才寫得出情境

規格寫到一半,出現了一個問題。

Claude Code 一開始跟我說,從首頁「+」丟的事和從行事曆建立的行程是兩種資料,所以權限可以分開設定;我照這個說法先回答了權限相關的題目。等到要寫「對方可不可以改」的情境時,它得把每一條規則對到實際的資料欄位和安全規則,才發現兩個入口建立的其實是同一種資料,存在同一個地方,程式碼裡沒辦法分辨是從哪裡建立的。

前提錯了,前面的答案也就不成立。所以 Claude Code 把更正寫進問題紀錄,重新問我一次,這次我選了跟現有行為一致的做法:安全規則不需要修改,既有的測試也不用動。

照錯的前提寫出來的第一版規格裡,排了修改安全規則、改寫一條既有測試的任務;如果跳過規格直接實作,這些改動會直接進到程式碼。規格這一步的用處,就是讓 AI 在動手前先把需求對到程式碼,錯的前提在文件階段就會被發現。

3. 分析工具抓出「需求沒有對應的任務」

第一份規格的四份文件寫完,spectra validate 格式檢查通過;接著跑 spectra analyze,出現 8 個 Warning:

Requirement '…' has no matching task
Design topic '…' not referenced in tasks

前 4 個是 spec 裡的需求,在 tasks 裡找不到對應的任務;後 4 個是 design 裡的技術決定,tasks 沒有提到。任務其實都有寫,只是沒有標出它在實作哪一條需求。

於是在每一項任務後面補上對應的需求與設計名稱:

- [ ] 1.2 ...(Requirement: <name>;Design: <topic>)

再跑一次 spectra analyze,Warning 歸零。這個對照讓實作的 AI 看到任務時,知道要回頭讀哪一段規格。

4. iOS 26 的確認對話框,改從按鈕長出來

刪除一筆資料前要跳出確認,SwiftUI 用 confirmationDialog。iOS 18 以前,它在 iPhone 上是從畫面底部升起的 action sheet;iOS 26 改成從觸發它的元件長出來。Apple 的 Liquid Glass 採用指南寫得很清楚:

An action sheet originates from the element that initiates the action, instead of from the bottom edge of the display.

同一份指南也提醒要指定來源:

Specify the source of an action sheet. Position an action sheet’s anchor next to the control it originates from.

在 SwiftUI 裡,「來源」就是 .confirmationDialog 掛在哪一個 view 上。Claude Code 第一次把它掛在整個詳情頁的內容上,對話框就從畫面中間長出來,箭頭指向內容正中央。改掛到工具列的「更多」按鈕之後,對話框就從按鈕旁邊出現:

Menu { /* edit, delete */ } label: { Image(systemName: "ellipsis") }
    .confirmationDialog("delete.title", isPresented: $isConfirming, titleVisibility: .visible) {
        Button("delete.confirm", role: .destructive, action: delete)
        Button("delete.keep", role: .cancel) {}
    }

左:詳情頁左上角的「更多」按鈕;右:iOS 26 的刪除確認從同一個按鈕長出來,不是從畫面底部升起

另外一個差異:在模擬器上實際看到的 popover 樣式沒有顯示取消按鈕,點對話框外面就是取消。

5. UI 測試也要跟著改

呈現方式一變,原本的 UI 測試寫法就找不到按鈕:

  • 確定按鈕在 XCUITest 的元素樹裡巢狀出現兩層,identifier 相同,直接 tap() 會因為找到多個符合的元素(Multiple matching elements)而失敗。
  • 沒有取消按鈕可以點,要點對話框外面的區域。

Claude Code 的改法:

// popover nests the button twice with the same identifier
app.buttons.matching(identifier: "deleteConfirm").firstMatch.tap()

// cancel = tap outside the popover
app.otherElements["PopoverDismissRegion"].firstMatch.tap()

PopoverDismissRegion 是 popover 外圍那塊區域在元素樹裡的名稱,UI 測試點它,效果跟使用者點外面一樣。

6. 先寫測試,會遇到的兩個狀況

這次照規格要求先寫測試、確認紅燈,再寫實作。Claude Code 回報了兩個實際遇到的狀況。

第一個:同一個 test target 裡,只要有一個測試檔編譯不過,整個 target 的測試都跑不起來。Swift 的測試 target 是一整個 module 一起編譯,所以「先寫測試」在 Swift 裡的紅燈,常常是編譯錯誤,不是測試失敗。兩個 change 的測試放在同一個 target,只好先把其中一個的實作補齊,另一個的 UI 測試才跑得起來。

第二個:刪除成功後,畫面會閃一下「找不到這件事」。原因是 Firestore 的即時監聽先把資料從串流移除,詳情頁還沒關,畫面就先重畫成找不到。解法是 ViewModel 在刪除前留一份快照,關閉前都顯示快照:

var displayedCard: Card? { card ?? cardBeingDeleted }

7. 目前的狀態

項目結果
規格2 份,共 14 項任務,全部完成
TeammateKit 測試48 個
安全規則測試79 個,沒有修改
App 測試142 個(新增 20 個)
verify.shVERIFY: PASS
截圖審查Light、Dark、大字級都截圖自審,沒有 P0、P1

還沒做的部分:

  • 裝到兩支 iPhone(開發者模式、跟 Mac 配對、用腳本裝機),改到 Day 18

明天

Day 17 看 Apple 官方的 iPhone Duo 影片與工作坊,整理官方資源、設計原意,評估神隊友要怎麼調整。

參考資源

官方文件

分享

iThome 鐵人賽 Day 16:iOS 26 confirmationDialog 行為變更,從來源按鈕彈出,XCUITest 要跟著調整
iThome 鐵人賽 Day 16:iOS 26 confirmationDialog 行為變更,從來源按鈕彈出,XCUITest 要跟著調整

SwiftUI 的 confirmationDialog 一行程式都沒改,到了 iOS 26 卻改從觸發它的按鈕彈出。這篇記錄修卡片編輯與刪除時遇到的呈現變更、UI 測試為什麼找不到按鈕,以及先寫規格再讓 AI 實作時,spectra analyze 抓出的漏洞。

Gooliya 鼓櫟數位
Steven Chang

Steven Chang

軟體工程師,平常主要有個人接案、DevOps、系統開發、維運。研究 AI 是為了省下時間陪小朋友。

專案卡住了,或是想把重複的工作交出去?

我做 AI 專案救援、系統架構開發、AI Agent 與流程自動化。先聊聊你的情況,不用先想清楚要做什麼。

免費訂閱 Gooliya 鼓櫟數位電子報

雜談 AI、開發與自動化,新文章上線時寄給你。

訂閱服務由 Kit 提供,Email 只用來寄送新文章