Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
183 views
in Technique[技术] by (71.8m points)

ios - How can I load an UIImage into a SwiftUI Image asynchronously?

In SwiftUI there are some .init methods to create an Image but none of them admits a block or any other way to load an UIImage from network/cache...

I am using Kingfisher to load images from network and cache inside a list row, but the way to draw the image in the view is to re-render it again, which I would prefer to not do. Also, I am creating a fake image(only coloured) as placeholder while the image gets fetched. Another way would be to wrap all inside a custom view and only re-render the wrapper. But I haven't tried yet.

This sample is working right now. Any idea to improve the current one will be great

Some view using the loader

struct SampleView : View {

    @ObjectBinding let imageLoader: ImageLoader

    init(imageLoader: ImageLoader) {
        self.imageLoader = imageLoader
    }

    var body: some View {
       Image(uiImage: imageLoader.image(for: "https://url-for-image"))
          .frame(width: 128, height: 128)
          .aspectRatio(contentMode: ContentMode.fit)
    }

}
import UIKit.UIImage
import SwiftUI
import Combine
import class Kingfisher.ImageDownloader
import struct Kingfisher.DownloadTask
import class Kingfisher.ImageCache
import class Kingfisher.KingfisherManager

class ImageLoader: BindableObject {

    var didChange = PassthroughSubject<ImageLoader, Never>()
    private let downloader: ImageDownloader
    private let cache: ImageCache
    private var image: UIImage? {
        didSet {
            dispatchqueue.async { [weak self] in
                guard let self = self else { return }
                self.didChange.send(self)
            }
        }
    }
    private var task: DownloadTask?
    private let dispatchqueue: DispatchQueue

    init(downloader: ImageDownloader = KingfisherManager.shared.downloader,
         cache: ImageCache = KingfisherManager.shared.cache,
         dispatchqueue: DispatchQueue = DispatchQueue.main) {
        self.downloader = downloader
        self.cache = cache
        self.dispatchqueue = dispatchqueue
    }

    deinit {
        task?.cancel()
    }

    func image(for url: URL?) -> UIImage {
        guard let targetUrl = url else {
            return UIImage.from(color: .gray)
        }
        guard let image = image else {
            load(url: targetUrl)
            return UIImage.from(color: .gray)
        }
        return image
    }

    private func load(url: URL) {
        let key = url.absoluteString
        if cache.isCached(forKey: key) {
            cache.retrieveImage(forKey: key) {  [weak self] (result) in
                guard let self = self else { return }
                switch result {
                case .success(let value):
                    self.image = value.image
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        } else {
            downloader.downloadImage(with: url, options: nil, progressBlock: nil) {  [weak self] (result) in
                guard let self = self else { return }
                switch result {
                case .success(let value):
                    self.cache.storeToDisk(value.originalData, forKey: url.absoluteString)
                    self.image = value.image
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
    }

}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Pass your Model to ImageRow struct which contains url.

import SwiftUI
import Combine

struct ContentView : View {
    var listData: Post
    var body: some View {
        List(model.post) { post in
            ImageRow(model: post) // Get image
        }
    }
}

/********************************************************************/
// Download Image

struct ImageRow: View {
    let model: Post
    var body: some View {
        VStack(alignment: .center) {
            ImageViewContainer(imageUrl: model.avatar_url)
        }
    }
}

struct ImageViewContainer: View {
    @ObjectBinding var remoteImageURL: RemoteImageURL

    init(imageUrl: String) {
        remoteImageURL = RemoteImageURL(imageURL: imageUrl)
    }

    var body: some View {
        Image(uiImage: UIImage(data: remoteImageURL.data) ?? UIImage())
            .resizable()
            .clipShape(Circle())
            .overlay(Circle().stroke(Color.black, lineWidth: 3.0))
            .frame(width: 70.0, height: 70.0)
    }
}

class RemoteImageURL: BindableObject {
    var didChange = PassthroughSubject<Data, Never>()
    var data = Data() {
        didSet {
            didChange.send(data)
        }
    }
    init(imageURL: String) {
        guard let url = URL(string: imageURL) else { return }

        URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard let data = data else { return }

            DispatchQueue.main.async { self.data = data }

            }.resume()
    }
}
/********************************************************************/

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...