大家好,鐵人賽堂堂邁入第二十六日!
昨天,我們的 App 已經能夠成功地將使用者的指令,透過 NetworkManager
發送到後端的 AI Agent。我們在 Xcode 的控制台中,也親眼看到了從伺服器回傳的 JSON 資料,雖然傳送了,但遇到 Could not connect to the server.的問題!不過沒關係,我們先完成 UI 的更新再來解決 Could not connect to the server的問題!
今天,我們將完成最後,也是最關鍵的一塊拼圖:我們要解析這些 JSON 回應,提取出 Agent 的回覆,並將其動態地新增到聊天畫面中,實現一個完整的、有來有回的對話體驗!
讓我們回到 MainViewController.swift
中的 runAgent
函式。在 try await
成功後,我們拿到了一個 SendMessageResponse
物件,它是一個 [RunResponseElement]
陣列。
回顧一下 Day 21 Adk.swift
中的定義,以及我們在 ADK 執行日誌中看到的結構,這個陣列可能包含多個元素,有些是工具呼叫的過程,有些是最終的文字答案。我們需要的,是那個包含了最終 text
回覆的元素。
// MainViewController.swift
func runAgent(for text: String) async {
do {
// ... (省略了組裝 requestBody 和發送請求的部分) ...
let response: SendMessageResponse = try await NetworkManager.shared.requestData(...)
// --- 處理回應的核心邏輯 ---
// 1. 設定一個預設的回覆訊息,以防萬一找不到答案
var botText = "抱歉,我無法處理您的請求。"
// 2. 從回應陣列中,找出最後一個包含有效文字內容的部分
// 我們使用 Swift 的高階函式 last(where:) 來尋找
if let finalAnswer = response.last(where: { $0.content.parts.first?.text != nil }) {
// 如果找到了,就取出它的文字內容
botText = finalAnswer.content.parts.first?.text ?? botText
}
// 3. 將 Bot 的回覆包裝成一個新的 Message 物件
let botMessage = Message(text: botText, sender: .bot)
// 4. 將 Bot 的訊息加入到我們的資料來源陣列中
messages.append(botMessage)
// 5. 通知 TableView 重新載入資料,並滾動到底部
tbvChat.reloadData()
scrollToBottom(animated: true)
} catch {
// ... (省略錯誤處理) ...
// 在這裡,我們也可以將錯誤訊息顯示在畫面上
let errorMessage = Message(text: "獲取回覆失敗: \(error.localizedDescription)", sender: .bot)
messages.append(errorMessage)
tbvChat.reloadData()
scrollToBottom(animated: true)
}
}
reloadData()
的魔法上面程式碼中最關鍵的兩行是:
messages.append(botMessage)
: 我們更新了 App 的「資料來源」。tbvChat.reloadData()
: 我們告訴 UITableView
:「嘿!我的資料變了,請你根據最新的 messages
陣列,重新問一次 numberOfRowsInSection
和 cellForRowAt
,然後把畫面重畫一遍!」這就是 iOS 開發中資料驅動 UI 的核心思想:我們不直接操作 UI 元件,而是操作資料,然後讓 UI 自動根據資料的變化來更新自己。
如果您有跟著專案檔案設定,您會發現在 AppDelegate.swift
中,我們引入了一個非常好用的第三方函式庫 IQKeyboardManagerSwift
。
// AppDelegate.swift
import IQKeyboardManagerSwift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 啟用 IQKeyboardManager
IQKeyboardManager.shared.isEnabled = true
// 點擊鍵盤以外的區域時,自動收起鍵盤
IQKeyboardManager.shared.resignOnTouchOutside = true
return true
}
// ...
}
只需要這幾行程式碼,IQKeyboardManagerSwift
就會自動幫我們處理惱人的鍵盤遮擋問題。當你點擊 UITextField
時,鍵盤會自動彈出,並且 UITableView
會自動向上推移,確保輸入框和對話內容不會被遮住。這極大地提升了 App 的使用者體驗。
恭喜你!今天,我們終於完成了整個 App 的互動閉環!我們學會了:
adk-api
回傳的複雜陣列資料,並從中提取關鍵資訊。
reloadData()
。
明天,我們將解決昨天我們遇到的 Could not connect to the server 的問題!