在 WWDC 2021 中,Apple 公布了 SwiftUI 框架的大量新功能,使開發者的開發 App 變得更得心應手。 AsyncImage 絕對是 iOS 15 其中一個最令人期待的功能。如果你的App需要從遠程伺服器加載和顯示圖像,這個新視圖應該可以讓你不必編寫自己的程式碼來處理非同步 (asynchronous) 下載。
AsyncImage 是用於非同步加載和顯示遠程圖像的內置視圖。 你只需要告訴它圖像的 URL, AsyncImage 就自動抓取遠程圖像並將其顯示在屏幕上。
在本章中,我將向你講解如何在 SwiftUI 項目中使用 AsyncImage。
使用 AsyncImage 的最簡單方法是像這樣指定圖像的 URL:
AsyncImage(url: URL(string: imageURL))
AsyncImage 就會自動連接到所指定的 URL 以非同步形式從遠程伺服器加載圖像。 當圖像尚未能夠顯示時,它還會自動將佔位符呈現為灰色。 完全下載後,AsyncImage 就會以其固有大小顯示圖像。

假設你已經在 Xcode 中建立了一個新的 SwiftUI 項目,你可以像這樣更改 ContentView 中的程式碼來試一試:
struct ContentView: View {
let imageURL = "https://link.appcoda.com/testimage"
var body: some View {
AsyncImage(url: URL(string: imageURL))
}
}
更改後,你應該會看到一個灰色的佔位符。數秒後,就會顯示下載的圖像。
如果要使圖像變小或變大,可以將縮放值傳給 scale 參數,如下所示:
AsyncImage(url: URL(string: imageURL), scale: 2.0)
要縮小圖像,你就指定一個大於 1.0 的值。 相反,小於 1 的值就會把圖像變大。

AsyncImage 也提供了另一個構造函數 (constructor),讓開發者可以進一步客製化圖像:
init<I, P>(url: URL?, scale: CGFloat, content: (Image) -> I, placeholder: () -> P)
我們可以使用上面的 init 初始化 AsyncImage,來調整及縮放下載好的圖像。更重要的是,我們可以實作自己的佔位符。看看以下的範例程式碼片段:
AsyncImage(url: URL(string: imageURL)) { image in
image
.resizable()
.scaledToFill()
} placeholder: {
Color.purple.opacity(0.1)
}
.frame(width: 300, height: 500)
.cornerRadius(20)
在上面的程式碼中,AsyncImage 提供了下載好的圖像。然後,我們應用 resizable() 和 scaledToFill() 修飾符來調整圖像尺寸,並把 AsyncImage 視圖的尺寸限制為 300×500 points。
placeholder 參數讓我們可以創建自己的佔位符,來取代預設的佔位符。在以下範例中,我們把佔位符設置為淺紫色。

如果你需要更精準操作非同步下載,AsyncImage 視圖提供了另一個init函數:
init(url: URL?, scale: CGFloat, transaction: Transaction, content: (AsyncImagePhase) -> Content
AsyncImagePhase 是一個列舉 (enum),用於跟踪下載操作的當前階段。 你可以針對每個階段提供詳細的實作,包括empty、failure 和success。
看看以下範例程式碼片段:
AsyncImage(url: URL(string: imageURL)) { phase in
switch phase {
case .empty:
Color.purple.opacity(0.1)
case .success(let image):
image
.resizable()
.scaledToFill()
case .failure(_):
Image(systemName: "exclamationmark.icloud")
.resizable()
.scaledToFit()
@unknown default:
Image(systemName: "exclamationmark.icloud")
}
}
.frame(width: 300, height: 300)
.cornerRadius(20)
在 Empty 的情況下,表示圖像未加載,於是我們會顯示一個佔位符。在 success 的情況下,我們就會應用幾個修飾符,並將圖像顯示在螢幕上。在 failure 的情況下,我們就可以在出現錯誤時提供備用視圖 (alternate view)。在上面的程式碼中,我們就這樣顯示了一個系統圖像。

同一個 init 可以讓我們在階段更改時指定可選 transaction。例如,以下程式碼片段在 transaction 參數中指定使用 spring 類型的動畫:
AsyncImage(url: URL(string: imageURL), transaction: Transaction(animation: .spring())) { phase in
switch phase {
case .empty:
Color.purple.opacity(0.1)
case .success(let image):
image
.resizable()
.scaledToFill()
case .failure(_):
Image(systemName: "exclamationmark.icloud")
.resizable()
.scaledToFit()
@unknown default:
Image(systemName: "exclamationmark.icloud")
}
}
.frame(width: 300, height: 500)
.cornerRadius(20)
如此一來,在下載圖像後,我們就會看到淡入 (fade-in) 動畫。我們無法在預覽版面中測試程式碼,請在模擬器中測試程式碼以查看動畫。
你也可以把 transition 修飾符附加到 image 視圖:
case .success(let image):
image
.resizable()
.scaledToFill()
.transition(.slide)
這樣在顯示結果圖像時,就會看到滑入 (slide-in) 動畫。
在本章中,我們介紹了一個非常好用的「AsyncImage」視圖。有了這個新功能,下載和顯示遠程圖像(Remote Image)時都變得非常容易。 你只需要指定圖像的 URL,AsyncImage視圖就會為你完成所有繁重的工作。
在本章所準備的範例檔中,有最後完整的 Xcode 專案,可供你下載參考:
https://www.appcoda.com/resources/swiftui5/SwiftUIAsyncImage.zip