Reputation: 175
I have a problem when I want to show an Image from a URL. I created a class for downloading data and publishing the data forward - ImageLoader
:
class ImageLoader: ObservableObject {
var didChange = PassthroughSubject<Data, Never>()
var data = Data() {
didSet {
didChange.send(data)
}
}
func loadData(from urlString: String?) {
if let urlString = urlString {
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
self.data = data
}
}
task.resume()
}
}
}
Therefore, I use it inside a ImageView
struct which I use inside my screen.
struct ImageView: View {
var urlString: String
@ObservedObject var imageLoader: ImageLoader = ImageLoader()
@State var image: UIImage = UIImage(named: "homelessDogsCats")!
var body: some View {
ZStack() {
Image(uiImage: image)
.resizable()
.onReceive(imageLoader.didChange) { data in
self.image = UIImage(data: data) ?? UIImage()
}
}.onAppear {
self.imageLoader.loadData(from: urlString)
}
}
}
My problem is that if I just run my project, the image doesn't change and by default appears only image UIImage(named: "homelessDogsCats")
.
If I add a breakpoint inside
onAppear {
self.imageLoader.loadData(from: urlString)
}
and just step forward, the image is showing.
I have the same problem in another view which usually doesn't display the Image
from URL, but sometimes it does.
Upvotes: 2
Views: 7523
Reputation: 12383
Working version for iOS 13, 14 (AsyncImage
1 liner is introduced in iOS 15, and the solution below is for the versions prior to that in case your min deployment target is not iOS 15 yet ) and with the latest property wrappers - Observed, Observable and Publisher ( without having to use PassthroughSubject<Data, Never>()
Main View
import Foundation
import SwiftUI
import Combine
struct TransactionCardRow: View {
var transaction: Transaction
var body: some View {
CustomImageView(urlString: "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png") // This is where you extract urlString from Model ( transaction.imageUrl)
}
}
Creating CustomImageView
struct CustomImageView: View {
var urlString: String
@ObservedObject var imageLoader = ImageLoaderService()
@State var image: UIImage = UIImage()
var body: some View {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width:100, height:100)
.onReceive(imageLoader.$image) { image in
self.image = image
}
.onAppear {
imageLoader.loadImage(for: urlString)
}
}
}
Creating a service layer to download the Images from url string, using a Publisher
class ImageLoaderService: ObservableObject {
@Published var image: UIImage = UIImage()
func loadImage(for urlString: String) {
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
self.image = UIImage(data: data) ?? UIImage()
}
}
task.resume()
}
}
Upvotes: 2
Reputation: 54611
Try using @Published
- then you don't need a custom PassthroughSubject
:
class ImageLoader: ObservableObject {
// var didChange = PassthroughSubject<Data, Never>() <- remove this
@Published var data: Data?
...
}
and use it in your view:
struct ImageView: View {
var urlString: String
@ObservedObject var imageLoader = ImageLoader()
@State var image = UIImage(named: "homelessDogsCats")!
var body: some View {
ZStack() {
Image(uiImage: image)
.resizable()
.onReceive(imageLoader.$data) { data in
guard let data = data else { return }
self.image = UIImage(data: data) ?? UIImage()
}
}.onAppear {
self.imageLoader.loadData(from: urlString)
}
}
}
Note: if you're using SwiftUI 2, you can use @StateObject
instead of @ObservedObject
and onChange
instead of onReceive
.
struct ImageView: View {
var urlString: String
@StateObject var imageLoader = ImageLoader()
@State var image = UIImage(named: "homelessDogsCats")!
var body: some View {
ZStack() {
Image(uiImage: image)
.resizable()
.onChange(of: imageLoader.data) { data in
guard let data = data else { return }
self.image = UIImage(data: data) ?? UIImage()
}
}.onAppear {
self.imageLoader.loadData(from: urlString)
}
}
}
Upvotes: 3