iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
生成式 AI

三十天解鎖上下文超能力:MCP 實戰系列 第 26

Day 26 - 跨平台實戰 VI:處理回應與動態更新 UI,完成互動閉環

  • 分享至 

  • xImage
  •  

大家好,鐵人賽堂堂邁入第二十六日!

昨天,我們的 App 已經能夠成功地將使用者的指令,透過 NetworkManager 發送到後端的 AI Agent。我們在 Xcode 的控制台中,也親眼看到了從伺服器回傳的 JSON 資料,雖然傳送了,但遇到 Could not connect to the server.的問題!不過沒關係,我們先完成 UI 的更新再來解決 Could not connect to the server的問題!

今天,我們將完成最後,也是最關鍵的一塊拼圖:我們要解析這些 JSON 回應,提取出 Agent 的回覆,並將其動態地新增到聊天畫面中,實現一個完整的、有來有回的對話體驗!

一、解析複雜的 API 回應

讓我們回到 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)
    }
}

二、讓 UI 動起來!reloadData() 的魔法

上面程式碼中最關鍵的兩行是:

  • messages.append(botMessage): 我們更新了 App 的「資料來源」。
  • tbvChat.reloadData(): 我們告訴 UITableView:「嘿!我的資料變了,請你根據最新的 messages 陣列,重新問一次 numberOfRowsInSectioncellForRowAt,然後把畫面重畫一遍!」

這就是 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 的互動閉環!我們學會了:

  1. 如何解析 adk-api 回傳的複雜陣列資料,並從中提取關鍵資訊。
  2. 資料驅動 UI 的核心概念:更新資料來源,然後呼叫 reloadData()
  3. 如何利用第三方函式庫,輕鬆地解決 iOS 開發中的常見痛點 (鍵盤處理)。

明天,我們將解決昨天我們遇到的 Could not connect to the server 的問題!


上一篇
Day 25 - 跨平台實戰 V:讓 iOS App 活起來!發送第一筆 API 請求
下一篇
Day 27 - 跨平台實戰 VIl - ADK api_server 不只能開在本機!
系列文
三十天解鎖上下文超能力:MCP 實戰28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言