【SwiftUI】URL指定で画像を表示する方法【URLImage】

以前、こちらの記事で画像の表示方法を紹介しましたが、今回はWeb上の画像をURL指定して表示する方法を紹介します。

【SwiftUI】URL指定で画像を表示する方法【URLImage】

標準の Image にはURLを指定するイニシャライザーはありませんので、View プロトコルを継承してカスタマイズ View を作成します

また、MVVM を意識して、ViewViewModel に分けて実装してみました。

URLImageViewModel

import SwiftUI

final class URLImageViewModel: ObservableObject {
    
    @Published var downloadData: Data? = nil
    let url: String
    
    init(url: String, isSync: Bool = false) {
        self.url = url
        if isSync {
            self.downloadImageSync(url: self.url)
        } else {
            self.downloadImageAsync(url: self.url)
        }
    }
    
    func downloadImageAsync(url: String) {
        
        guard let imageURL = URL(string: url) else {
            return
        }
                
        DispatchQueue.global().async {
            let data = try? Data(contentsOf: imageURL)
            DispatchQueue.main.async {
                self.downloadData = data
            }
        }
    }
    
    func downloadImageSync(url: String) {
        
        guard let imageURL = URL(string: url) else {
            return
        }
        
        let data = try? Data(contentsOf: imageURL)
        self.downloadData = data
    }
}

先ずは ViewModel です。

非同期で取得する場合に対応するため、画像データの変数を @Publish で宣言し、データが格納されたタイミングで View が切り替わるようにしています。

@Published var downloadData: Data?

また、同期取得・非同期取得のそれぞれのメソッドを用意して、イニシャライザーでどちらを使うか指定します(デフォルトは非同期としています)。

init(url: String, isSync: Bool = false) { ... }
func downloadImageAsync(url: String) { ... }
func downloadImageSync(url: String) { ... }

URLImageView

続いて、View 側です。

import SwiftUI

struct URLImageView: View {
    
    @ObservedObject var viewModel: URLImageViewModel
    
    var body: some View {
        if let imageData = self.viewModel.downloadData {
            if let image = UIImage(data: imageData) {
                return Image(uiImage: image).resizable()
            } else {
                return Image(uiImage: UIImage()).resizable()
            }
        } else {
            return Image(uiImage: UIImage()).resizable()
        }
    }
}

先ほど作成した URLImageViewModel@ObservedObject 宣言で保持します。

そして、ダウンロードで取得した画像は Data 型で受け取っているので、UIImage に変換しそれを Image のイニシャライザーに指定しています。

if let image = UIImage(data: imageData) {
    return Image(uiImage: image).resizable()
}

使用例

最後に、使用例を載せておきます。

struct ContentView: View {
    var body: some View {
        URLImageView(viewModel: .init(url: "https://www.tobezoo.com/animals/img/mn03.jpg"))
            .frame(width: 256, height: 256)
            .clipShape(Circle())
    }
}

ちなみに、http 通信のダウンロードは iOS9 以降エラーとなってしまいますので https 通信のURLに限定しておきましょう。

どうしても http 通信で取得しなければならない場合は info.plist に例外登録をしておく必要があります

以上