昨天,我們成功讓 App 連上 NASA 伺服器並顯示了天文圖片,完成了一個完整的互動流程!但妳可能也發現了一個小缺憾:按下按鈕後,畫面會「凍結」一下才出現圖片,使用者根本不知道發生了什麼事。
一個專業的 App,必須隨時讓使用者清楚目前的狀態。今天,我們就要來為這個 App 加上畫龍點睛的一筆——讀取中的等待動畫!我們將學習如何使用 UIActivityIndicatorView
,在網路請求的空檔提供視覺回饋,並進一步優化我們的程式碼,讓 App 體驗更上一層樓。
UIActivityIndicatorView
loadImage
函式升級為使用 完成回呼(completion handler) 的版本,實現更清晰的職責分離先在 .xib
上放置(可參考先前的教學文章):
Activity Indicator View
→ 顯示「讀取中」的動畫Label
,並建立 IBOutlet
→ 顯示資料或錯誤訊息@IBOutlet weak var lbInfo: UILabel!
@IBOutlet weak var aivWait: UIActivityIndicatorView!
Activity Indicator View
可於右側屬性設定勾選Hides When Stopped
,這樣停止動畫會自動隱藏。
在發送 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
會自動隱藏ImageView
,再停止動畫將圖片下載與 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()
}
completion
可以在下載完成後做額外操作(更新 ImageView
、停止動畫)確認旋轉的 Activity Indicator
在抓取資料期間正確顯示,資料下載完成後自動停止:
Label
會顯示錯誤訊息,提醒使用者:Label
會顯示日期與圖片標題:嘗試快速更改 PickerView
的日期,確認旋轉動畫與圖片顯示能同步更新,不會出現舊圖片或動畫錯位的情況。
完成以上步驟,即可確定本次功能已正確實作,使用者體驗也更加順暢。
今天,我們為 App 的使用者體驗帶來了一次意義重大的升級!透過加入 UIActivityIndicatorView
,我們的 App 終於學會了在忙碌時跟使用者說一聲:「請稍候,我正在努力中!」
更重要的是,你將 loadImage
函式重構成使用 完成回呼(completion handler) 的專業寫法。這個「只管做事,做完再回報」的模式,是處理所有非同步任務的核心思想,也是你程式設計能力的一次飛躍。
結合了等待動畫與錯誤提示,我們的 NASA App 現在不僅功能完整,也變得更加專業與友善了!
朋友們,我們的 30 天鐵人賽旅程即將迎來最重要的「總驗收」!
在接下來所有剩下的時間裡,我們將專注於一個目標:將過去二十多天學的所有知識——從 UI 元件、網路請求到 MVC 架構——全部融會貫通,打造出一個功能完整的實戰專案。
這次我們不追求華麗的介面,而是專注在核心功能的實現上:一個能與 Google Gemini API 真實互動的 聊天室 App。重點在於親手處理使用者輸入、發送網路請求、並將 AI 的回覆顯示在畫面上,完成一個完整的資料循環。
這將是我們學習成果的最終檢驗。準備好一起打造一個「會思考」的 App 了嗎?
敬請期待《Day 27|最終專案實戰:打造你的 Gemini AI 聊天室!(一)》