0
votes

I'm loading data from an API, and expecting my app to show the data once it's loaded.

In my View Model file, here's the code: It calls a WeatherService to get the data, and populates the weather property. Weather is a struct in this case.

class WeatherViewModel: ObservableObject {

    let webService = WeatherService.shared
    @Published var weather:Weather?
    
    init() {
        
    }

    func getWeather() {
        webService.getWeather { weather in
            if let weather = weather {
                self.weather = weather
            }
        }
    }
    
}

In my SwiftUI view, here's the code:

  • I instantiate an instance of the View Model as an ObservedObject
  • In the inAppear, I call the method in the view model to get the data
  • The first time the screen launches (using a tab bar), I see "Loading weather..." and it never goes away
  • If I navigate to a different tab and back, I see the weather. I can't tell if this is data from the old API call, or from the new one.
struct WeatherView: View {
    
    @ObservedObject var weatherViewModel = WeatherViewModel()
    @State var areDetailsHidden = true
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            
            if(weatherViewModel.weather == nil) {
                Text("Loading weather...")
            } else {
                Text("Display the weather here")
            }
        }
        .onAppear{
            self.weatherViewModel.getWeather()
        }
    }
}

The weird thing is, if I remove the getWeather() from the onAppear and add it to the init() of the View Model, it works (although for some reason getWeather() gets called twice...). However, I want the weather info to be refreshed every time the screen is loaded.

1
The problem you have hear is that the weatherViewModel is owned by the WeatherView. When it changes that causes the view to be rendered again which creates a new weatherViewModel and you are in an endless loop; You need to move the model out of the view. - Upholder Of Truth
@UpholderOfTruth that did it! I turned the weatherViewModel into an environment object and that solved it. I was under the impression that the body was got rendered again, and the properties remain as is. Interesting. If you submit this as an answer I'll mark it as such. - Khuffie

1 Answers

0
votes

This is caused by the:

@ObservedObject var weatherViewModel = WeatherViewModel()

being owned by the WeatherView itself.

So what happens is the weather view model changes which forces a re-render of the view which creates a new copy of the weather view model, which changes forces a re-render...

So you end up with an endless loop.

To fix it you need to move the weather view model out of the view itself so either use an @Binding and pass it in or an @EnvironmentObject and access it that way.