SwiftUI 新功能:利用 AsyncImage 非同步加載和顯示 Remote Image


在 WWDC 2021,Apple 為 SwiftUI 框架添加了大量新功能,減輕開發者的工作。在 iOS 15 中,AsyncImage 絕對是其中一個值得探討的新視圖。如果你的 App 需要從遠程伺服器加載和顯示圖像,有了這個新視圖,你就不需要編寫自己的程式碼來處理非同步 (asynchronous) 下載。

AsyncImage 是一個內置視圖,用於非同步加載和顯示 Remote Image。我們只需要輸入圖像 URL,AsyncImage 就會抓取 Remote Image,並將其顯示在螢幕上。

在這篇教學文章中,我們會看看如何在 SwiftUI 專案中使用 AsyncImage。要跟隨這篇教學的步驟實作,請使用 Xcode 13 和 iOS 15。

AsyncImage 的基本使用

使用 AsyncImage 最簡單的方法,就是如此指定圖像的 URL:

AsyncImage(url: URL(string: imageURL))

然後,AsyncImage 就會連接到你提供的 URL,並非同步地下載 Remote Image。如果圖像尚未準備好顯示,它會自動將佔位符 (placeholder) 呈現為灰色。圖像完全下載好後,AsyncImage 就會以其固有尺寸 (intrinsic size) 顯示圖像。

asyncimage-placeholder

如果想調整圖像的尺寸,我們可以這樣傳遞比例數值 (scaling value) 給 scale 參數 (parameter):

AsyncImage(url: URL(string: imageURL), scale: 2.0)

比例數值大於 1.0 就會縮小圖像,相反,比例數值少於 1 就會放大圖像。

asyncimage-scale-image

客製化圖像尺寸和佔位符

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-swiftui-change-image-size

處理非同步操作的不同階段 (Phase)

如果你想更好地控制非同步下載操作,AsyncImage 視圖就提供了另一個構造函數:

init(url: URL?, scale: CGFloat, transaction: Transaction, content: (AsyncImagePhase) -> Content)

AsyncImagePhase 是一個列舉 (enum),用於追踪下載操作的當前階段。你可以針對每個階段提供詳細的實作,包括 emptyfailure 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)。在上面的程式碼中,我們就這樣顯示了一個系統圖像。

swiftui-asyncimage-phase

利用 Transaction 來添加動畫

同一個 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) 動畫。

swiftui-asyncimage-animation

譯者簡介:Kelly Chan-AppCoda 編輯小姐。
原文Using AsyncImage in SwiftUI for Loading Images Asynchronously


軟件工程師,AppCoda 創辦人。著有《iOS 13 App 程式設計實力超進化實戰攻略》、《iOS 13 App 程式設計實力超進化實戰攻略》以及《精通 SwiftUI》。曾任職於HSBC, FedEx等公司,專責軟體開發、系統設計。2012年創立AppCoda技術部落格,定期發表iOS程式教學文章。現時專注發展AppCoda,致力於iOS程式教學,產品設計及開發。

blog comments powered by Disqus
Shares
Share This