Reputation: 18184
I have a Game-object that may hold an image. Whenever an image URL is found for a game a new instance of GameImage-object should be created. It will then fetch the image and populate the UIImage property. When this happens the UI should be updated presenting the image.
class Game: ObservableObject {
@Published var image: GameImage?
}
class GameImage: ObservableObject {
let url: URL
@Published var image: UIImage?
private var cancellable: AnyCancellable?
init(url: URL) {
self.url = url
}
func fetch() {
self.cancellable = URLSession.shared.dataTaskPublisher(for: self.url)
.map { UIImage(data: $0.data) }
.replaceError(with: nil)
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] (image) in
guard let self = self else { return }
self.image = image
print(self.url)
print(self.image)
})
}
func cancel() {
cancellable?.cancel()
}
deinit {
cancel()
}
}
struct ContentView: View {
@StateObject var game = Game()
var body: some View {
VStack {
if let image = game.image?.image {
Image(uiImage: image)
} else {
Text("No image.")
}
}
.onAppear(perform: {
guard let gameImageURL = URL(string: "https://cf.geekdo-images.com/itemrep/img/oVEpcbtyWkJjIjk1peTJo6hI1yk=/fit-in/246x300/pic4884996.jpg") else { return }
game.image = GameImage(url: gameImageURL)
game.image!.fetch()
})
}
}
The problem is. After fetch is done the debug console will show that image contains an UIImage. However the UI does not update to show the image. What am I missing here?
Upvotes: 3
Views: 386
Reputation: 257493
There is much more simpler solution than chaining ObservableObject
, just separate dependent part into standalone subview... and all will work automatically.
Here is possible approach. Tested with Xcode 12 / iOS 14.
struct ContentView: View {
@StateObject var game = Game()
var body: some View {
VStack {
if nil != game.image {
GameImageView(vm: game.image!)
}
}
.onAppear(perform: {
guard let gameImageURL = URL(string: "https://cf.geekdo-images.com/itemrep/img/oVEpcbtyWkJjIjk1peTJo6hI1yk=/fit-in/246x300/pic4884996.jpg") else { return }
game.image = GameImage(url: gameImageURL)
game.image!.fetch()
})
}
}
struct GameImageView: View {
@ObservedObject var vm: GameImage
var body: some View {
if let image = vm.image {
Image(uiImage: image)
} else {
Text("No image.")
}
}
}
Upvotes: 1