1

On the click of a button I am trying to download a new random image and update the view. When the app loads it displays the downloaded image. When the button is clicked the image seems to download but the view is never updated and displays the place holder image. Am I missing something here, any ideas? Here is a simplified version.

import SwiftUI

struct ContentView : View {

    @State var url = "https://robohash.org/random.png"

    var body: some View {
        VStack {
            Button(action: {
                self.url = "https://robohash.org/\(Int.random(in:0 ..< 10)).png"
            }) {
                Text("Get Random Robot Image")
            }
            URLImage(url: url)
        }

    }
}
class ImageLoader: BindableObject {

    var downloadedImage: UIImage?
    let didChange = PassthroughSubject<ImageLoader?, Never>()

    func load(url: String) {

        guard let imageUrl = URL(string: url) else {
            fatalError("Image URL is not correct")
        }

        URLSession.shared.dataTask(with: imageUrl) { data, response, error in
            guard let data = data, error == nil else {
                DispatchQueue.main.async {
                    self.didChange.send(nil)
                }
                return
            }

            self.downloadedImage = UIImage(data: data)
            DispatchQueue.main.async {
                print("downloaded image")
                self.didChange.send(self)
            }
        }.resume()
    }
}

import SwiftUI

struct URLImage : View {

    @ObjectBinding private var imageLoader = ImageLoader()
    var placeholder: Image

    init(url: String, placeholder: Image = Image(systemName: "photo")) {
        self.placeholder = placeholder
        self.imageLoader.load(url: url)
    }

    var body: some View {
        if let uiImage = self.imageLoader.downloadedImage {
            print("return downloaded image")
            return Image(uiImage: uiImage)
        } else {
            return placeholder
        }
    }
}

1 Answer 1

1

The problem seems to be related to some kind of lost synchronization between the ContentView and the ImageURL (that happens after the button click event).

A possible workaround is making the ImageURL a @State property of the ContentView.

After that, inside the scope of the button click event, we can call the image.imageLoader.load(url: ) method. As the download of the image ends, the publisher (didChange) will notify the ImageURL and then the change is correctly propagated to the ContentView.

import SwiftUI
import Combine

enum ImageURLError: Error {
    case dataIsNotAnImage
}

class ImageLoader: BindableObject {
    /*
    init(url: URL) {
        self.url = url
    }

    private let url: URL */

    let id: String = UUID().uuidString
    var didChange = PassthroughSubject<Void, Never>()

    var image: UIImage? {
        didSet {
            DispatchQueue.main.async {
                self.didChange.send()
            }
        }
    }

    func load(url: URL) {
        print(#function)
        self.image = nil

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

            guard
                let data = data,
                let image = UIImage(data: data)
            else {
                return
            }

            self.image = image
        }.resume()
    }

}

URLImage view:

struct URLImage : View {

    init() {
        self.placeholder = Image(systemName: "photo")
        self.imageLoader = ImageLoader()
    }

    @ObjectBinding var imageLoader: ImageLoader

    var placeholder: Image

    var body: some View {
        imageLoader.image == nil ?
            placeholder : Image(uiImage: imageLoader.image!)
    }
}

ContentView:

struct ContentView : View {

    @State var url: String = "https://robohash.org/random.png"
    @State var image: URLImage = URLImage()

    var body: some View {
        VStack {
            Button(action: {
                self.url = "https://robohash.org/\(Int.random(in: 0 ..< 10)).png"
                self.image.imageLoader.load(url: URL(string: self.url)!)
            }) {
                Text("Get Random Robot Image")
            }

            image
        }
    }
}

Anyway I will try to investigate the problem and if I will know something new I will modify my answer.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.