I have a view which I use throughout my app on various pages. The view shows an image and a label, in this case the city name.
import SwiftUI
import Kingfisher
struct CityView: View {
@EnvironmentObject private var cityViewModel: CityViewModel
@Binding var city: City
var body: some View {
NavigationLink(destination: CityDetailView(cityId: city.id)) {
VStack {
KFImage(URL(string: city.photoUrl ?? ""))
.resizable()
.background(Color.white)
Text(city.name)
}
}
}
}
Now, if the API response sends a nil
image, I want to call a separate endpoint to fetch the image url. This endpoint is currently set up to fetch a stock photo url and return it.
Right now, I have two different pages:
- A page that shows a list of random cities
- A profile page that shows a list of cities that the user has saved
For the page that shows a list of random cities, I have the following view model:
class CityViewModel: ObservableObject {
let cityService: CityServiceProtocol
@Published var cities: [City] = []
func fetchPhoto(cityId: Int) async {
do {
let data = try await cityService.getPhoto(cityId: cityId)
guard let photoUrl = data.data?.photoUrl else { return }
// Update the image url
if let index = self.cities.firstIndex(where: { $0.id == cityId }) {
self.cities[index].photoUrl = photoUrl
}
} catch {
print("Failed")
}
}
}
As you can see, I already have the fetchPhoto()
function here which updates the city image url once returned.
For the profile page that shows a list of cities that the user has saved, I have the following view model:
class ProfileViewModel: ObservableObject {
let profileService: ProfileServiceProtocol
@Published var savedCities: [City] = []
// excluded function that fetches saved cities from the api
}
Now I’ve run into a problem where, if the user navigates to their profile to view their saved cities, and a city doesn’t have an image url, it doesn’t currently fetch the image from the api.
If I want to keep this structure of using two view models, would I have to copy the fetchPhoto()
function from the CityViewModel to the ProfileViewModel, or is it possible to have one function that updates both view models?
2
Answers
You could have one model store object that is shared between the views. The async funcs could be in a controller struct (usually in the environment so it can be mocked for previews) and can be called from both views (in
.task(id:)
) and pass in the model object for it to update. EgThe logic could look something like this.
Even if you had to make a second protocol to support it. If you post the two protocols there may be a way to merge them though.