iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
Mobile Development

Swift iOS 開發新手村:從入門到 AI 聊天室系列 第 26

Day 26|Xcode 擴充功能:PickerView & NASA 等待動畫與 UI 優化

  • 分享至 

  • xImage
  •  

昨天,我們成功讓 App 連上 NASA 伺服器並顯示了天文圖片,完成了一個完整的互動流程!但妳可能也發現了一個小缺憾:按下按鈕後,畫面會「凍結」一下才出現圖片,使用者根本不知道發生了什麼事。

一個專業的 App,必須隨時讓使用者清楚目前的狀態。今天,我們就要來為這個 App 加上畫龍點睛的一筆——讀取中的等待動畫!我們將學習如何使用 UIActivityIndicatorView,在網路請求的空檔提供視覺回饋,並進一步優化我們的程式碼,讓 App 體驗更上一層樓。

今日學習重點

  • 提升使用者體驗:了解為何需要「等待提示」,並學會使用 UIActivityIndicatorView
  • 管理動畫生命週期:掌握在網路請求開始時啟動動畫、結束後停止動畫的邏輯
  • 重構非同步程式碼:將 loadImage 函式升級為使用 完成回呼(completion handler) 的版本,實現更清晰的職責分離
  • 清楚地處理錯誤:在網路請求失敗時,於介面上向使用者顯示明確的錯誤訊息。

一、新增元件並建立 IBOutlet

先在 .xib 上放置(可參考先前的教學文章):

  • Activity Indicator View → 顯示「讀取中」的動畫
  • Label,並建立 IBOutlet → 顯示資料或錯誤訊息
@IBOutlet weak var lbInfo: UILabel!
@IBOutlet weak var aivWait: UIActivityIndicatorView!

Activity Indicator View 可於右側屬性設定勾選 Hides When Stopped,這樣停止動畫會自動隱藏。

二、修改 fetchData() 加入動畫

在發送 API 請求前啟動動畫,下載完成或失敗後停止動畫:

func fetchData() {
    guard let year = selectedYear,
          let month = selectedMonth,
          let day = selectedDay else { return }
        
    let dateString = String(format: "%04d-%02d-%02d", year, month, day)
    print("Fetch NASA data for date: \(dateString)")
        
    // 顯示等待動畫
    lbInfo.text = "讀取中..."
    aivWait.isHidden = false
    aivWait.startAnimating()
    imgNASA.image = nil // 先清空舊圖片
        
    NetworkManager.shared.fetchNASAData(for: dateString) { [weak self] result in
        DispatchQueue.main.async {
            guard let self = self else { return }
            
            switch result {
            case .success(let nasaData):
                // 更新文字
                self.lbInfo.text = "\(nasaData.date)\n\(nasaData.title)"
                    
                // 下載圖片 → 設定到 UIImageView → 停止動畫
                self.loadImage(from: nasaData.url) { image in
                    self.imgNASA.image = image
                    self.aivWait.stopAnimating()
                    self.aivWait.isHidden = true
                }
                    
            case .failure(let error):
                self.lbInfo.text = "讀取失敗:\(error.localizedDescription)"
                self.aivWait.stopAnimating()
                self.aivWait.isHidden = true
            }
        }
    }
}
  • startAnimating():顯示旋轉動畫
  • stopAnimating():停止動畫,若勾選 Hides When Stopped 會自動隱藏
  • API 請求期間告知使用者目前狀態為「讀取中...」
  • 成功下載圖片後更新 ImageView,再停止動畫

三、優化 loadImage() 顯示

將圖片下載與 UI 更新拆開,使用 completion callback

func loadImage(from urlString: String, completion: @escaping (UIImage?) -> Void) {
    guard let url = URL(string: urlString) else {
        print("URL 格式錯誤")
        completion(nil)
        return
    }
        
    URLSession.shared.dataTask(with: url) { data, _, error in
        if let error = error {
            print("下載圖片失敗: \(error.localizedDescription)")
            DispatchQueue.main.async {
                completion(nil)
            }
            return
        }
            
        guard let data = data, let image = UIImage(data: data) else {
            print("圖片資料為空或無法轉換成 UIImage")
            DispatchQueue.main.async {
                completion(nil)
            }
            return
        }
            
        // 成功下載,回傳圖片
        DispatchQueue.main.async {
            completion(image)
        }
    }.resume()
}
  • 非同步下載圖片,避免 UI 卡住
  • completion 可以在下載完成後做額外操作(更新 ImageView、停止動畫)
  • 在發送請求前清空 ImageView,可避免顯示上一張圖片

四、測試模擬器

1. 選擇日期並抓取圖片

確認旋轉的 Activity Indicator 在抓取資料期間正確顯示,資料下載完成後自動停止:
https://ithelp.ithome.com.tw/upload/images/20251010/20177542ZYc9KpI46p.png

2. 檢查資料顯示

  • 若讀取失敗,Label 會顯示錯誤訊息,提醒使用者:
    https://ithelp.ithome.com.tw/upload/images/20251010/20177542LYuPQP1Muh.png
  • 若成功抓取,Label 會顯示日期與圖片標題:
    https://ithelp.ithome.com.tw/upload/images/20251010/20177542sKJeNo16vQ.png

3. 快速切換日期測試

嘗試快速更改 PickerView 的日期,確認旋轉動畫與圖片顯示能同步更新,不會出現舊圖片或動畫錯位的情況。

完成以上步驟,即可確定本次功能已正確實作,使用者體驗也更加順暢。

小結一下

今天,我們為 App 的使用者體驗帶來了一次意義重大的升級!透過加入 UIActivityIndicatorView,我們的 App 終於學會了在忙碌時跟使用者說一聲:「請稍候,我正在努力中!」

更重要的是,你將 loadImage 函式重構成使用 完成回呼(completion handler) 的專業寫法。這個「只管做事,做完再回報」的模式,是處理所有非同步任務的核心思想,也是你程式設計能力的一次飛躍。

結合了等待動畫與錯誤提示,我們的 NASA App 現在不僅功能完整,也變得更加專業與友善了!

🌟 明日預告

朋友們,我們的 30 天鐵人賽旅程即將迎來最重要的「總驗收」!

在接下來所有剩下的時間裡,我們將專注於一個目標:將過去二十多天學的所有知識——從 UI 元件、網路請求到 MVC 架構——全部融會貫通,打造出一個功能完整的實戰專案。

這次我們不追求華麗的介面,而是專注在核心功能的實現上:一個能與 Google Gemini API 真實互動的 聊天室 App。重點在於親手處理使用者輸入、發送網路請求、並將 AI 的回覆顯示在畫面上,完成一個完整的資料循環。

這將是我們學習成果的最終檢驗。準備好一起打造一個「會思考」的 App 了嗎?

敬請期待《Day 27|最終專案實戰:打造你的 Gemini AI 聊天室!(一)》


上一篇
Day 25|Xcode 滾動選取:PickerView & NASA 實戰應用(第二天)
下一篇
Day 27|最終專案實戰:打造你的 Gemini AI 聊天室!(一)
系列文
Swift iOS 開發新手村:從入門到 AI 聊天室27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言