I am trying to build an app using SwiftUI, and I've created a class responsible for performing an API request and decoding the response into a weatherResponse
object, and have annotated this object as @Published
within this class.
In my view, I have instantiated this class using the @StateObject
annotation. According to this post, this should be the correct way of observing the weatherResponse
object within my view so that it automatically updates.
However, when my view loads the instance of WeatherNetworking
hasn't even been initialized, and so the first reference to the object published within that class results in a nil value access.
struct MainWeatherView: View {
@StateObject var weatherNetworking = WeatherNetworking()
var body: some View {
VStack {
Image(systemName: weatherNetworking.getConditionName(weatherID: (weatherNetworking.weatherResponse!.current.weather[0].id))) // weatherResponse is nil
.resizable()
.aspectRatio( contentMode: .fit)
.scaleEffect(0.75)
.padding()
HStack {
VStack{
Text("Temperature")
.fontWeight(.bold)
.font(.system(size: 24))
Text("Humidity")
.fontWeight(.bold)
.font(.system(size: 24))
...
}
VStack {
Text("\(weatherNetworking.weatherResponse!.current.temp, specifier: "%.2f") °F")
.fontWeight(.bold)
.font(.system(size: 24))
Text("\(weatherNetworking.weatherResponse!.current.humidity, specifier: "%.0f") %")
.fontWeight(.bold)
.font(.system(size: 24))
...
}
}
}
.onAppear {
self.weatherNetworking.getMainWeather()
}
}
}
class WeatherNetworking: ObservableObject {
@StateObject var locationManager = LocationManager()
@Published var weatherResponse: WeatherResponse?
func getMainWeather() {
print("Location:", locationManager.lastLocation?.coordinate.latitude, locationManager.lastLocation?.coordinate.longitude)
if let loc = URL(string: "https://api.openweathermap.org/data/2.5/onecall?appid=redacted&exclude=minutely&units=imperial&lat=\(locationManager.lastLocation?.coordinate.latitude ?? 0)&lon=\(locationManager.lastLocation?.coordinate.longitude ?? 0)") {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: loc) { data, response, error in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(WeatherResponse.self, from: safeData)
DispatchQueue.main.async {
self.weatherResponse = results
}
print("weatherResponse was succesfully updated")
} catch {
print(error)
}
}
} else {
print(error!)
}
}
task.resume()
}
}
}
None of the print statements within getMainWeather()
execute which leads me to believe that the view is attempting to assign values before the function is called. How can I delay the assignment of these values within my view until after the onAppear()
method finishes? it is worth noting that just as MainWeatherView
depends upon the instantiation and asynchronous calls in WeatherNetworking
, so too WeatherNetworking
depends upon an instance of LocationManager
.
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
@Published var locationStatus: CLAuthorizationStatus?
@Published var lastLocation: CLLocation?
...
}