I have a view that displays a few photos that are loaded from an API in a scroll view. I want to defer fetching the images until the view is displayed. My view, simplified looks something like this:
struct DetailView : View {
@ObservedObject var viewModel: DetailViewModel
init(viewModel: DetailViewModel) {
self.viewModel = viewModel
}
var body: some View {
GeometryReader { geometry in
ZStack {
Color("peachLight").edgesIgnoringSafeArea(.all)
if self.viewModel.errorMessage != nil {
ErrorView(error: self.viewModel.errorMessage!)
} else if self.viewModel.imageUrls.count == 0 {
VStack {
Text("Loading").foregroundColor(Color("blueDark"))
Text("\(self.viewModel.imageUrls.count)").foregroundColor(Color("blueDark"))
}
} else {
VStack {
UIScrollViewWrapper {
HStack {
ForEach(self.viewModel.imageUrls, id: \.self) { imageUrl in
LoadableImage(url: imageUrl)
.scaledToFill()
}.frame(width: geometry.size.width, height: self.scrollViewHeight)
}.edgesIgnoringSafeArea(.all)
}.frame(width: geometry.size.width, height: self.scrollViewHeight)
Spacer()
}
}
}
}.onAppear(perform: { self.viewModel.fetchDetails() })
.onReceive(viewModel.objectWillChange, perform: {
print("Received new value from view model")
print("\(self.viewModel.imageUrls)")
})
}
}
my view model looks like this:
import Foundation
import Combine
class DetailViewModel : ObservableObject {
@Published var imageUrls: [String] = []
@Published var errorMessage : String?
private var fetcher: Fetchable
private var resourceId : String
init(fetcher: Fetchable, resource: Resource) {
self.resourceId = resource.id
// self.fetchDetails() <-- uncommenting this line results in onReceive being called + a view update
}
// this is a stubbed version of my data fetch that performs the same way as my actual
// data call in regards to ObservableObject updates
// MARK - Data Fetching Stub
func fetchDetails() {
if let path = Bundle.main.path(forResource: "detail", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let parsedData = try JSONDecoder().decode(DetailResponse.self, from: data)
self.imageUrls = parsedData.photos // <-- this doesn't trigger a change, and even manually calling self.objectWillChange.send() here doesn't trigger onReceive/view update
print("setting image urls to \(parsedData.photos)")
} catch {
print("error decoding")
}
}
}
}
If I fetch my data within the init
method of my view model, the onReceive
block on my view IS called when the @Published imageUrls
property is set. However, when I remove the fetch
from the init
method and call from the view using:
.onAppear(perform: { self.viewModel.fetchDetails() })
the onReceive
for viewModel.objectWillChange
is NOT called, even though the data is updated. I don't know why this is the case and would really appreciate any help here.