[iOS][SwiftUI]ネットワークのURLから画像表示（同期・非同期・キャッシュ・リンク・ウィジェット）

ウィジェット(widgets)内に画像を表示し、画像タップでURLスキームによるアプリ連携のために使用した**View**と**ViewModel**になります。


##ViewModel（画像データの同期・非同期・キャッシュ）
Does try? Data(contentsOf: URL) でキャッシュしてます
https://stackoverflow.com/a/57826757

```swift:URLImageViewModel.swift
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
        }
        
        let cache = URLCache.shared
        let request = URLRequest(url: URL(string: url)!, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad)
        if let data = cache.cachedResponse(for: request)?.data {
            self.downloadData = data
        }else {
            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 cache = URLCache.shared
        let request = URLRequest(url: URL(string: url)!, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad)
        if let data = cache.cachedResponse(for: request)?.data {
            self.downloadData = data
        }else {
            let data = try? Data(contentsOf: imageURL)
            self.downloadData = data
        }
    }
}
```

##View（画像表示）
```swift:URLImageView.swift
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().scaledToFit()
            } else {
                return Image(uiImage: UIImage()).resizable().scaledToFit()
            }
        } else {
            return Image(uiImage: UIImage()).resizable().scaledToFit()
        }
    }
}
```

##View（リンク付き画像表示）
```swift:LinkURLImageView.swift
import SwiftUI

struct LinkURLImageView: View {
    let url: URL
    let imageUrlString: String
    let isSyncURLImage: Bool

    init(url: URL, imageUrlString: String, isSyncURLImage: Bool = false) {
        self.url = url
        self.imageUrlString = imageUrlString
        self.isSyncURLImage = isSyncURLImage
    }

    var body: some View {
        Link(destination: url) {
            let imageViewModel = URLImageViewModel(url: imageUrlString, isSync: isSyncURLImage)
            URLImageView(viewModel: imageViewModel)
        }
    }
}
```


##使用例
ウィジェット(widgets)では非同期が使用できないため、同期通信を使用します。
（非同期はisSyncURLImageにfalseを指定）

```swift:WidgetEntryView.swift
import SwiftUI

    struct WidgetEntryView: View {
        var body: some View {
            LinkURLImageView(url: URL(string: "タップ先のリンクのURL（widgets://）")!,
                             imageUrlString: "画像のURL",
                             isSyncURLImage: true)
        }
}
```