I'm trying to update data in my viewModel here is my viewModel;
import SwiftUI
import CoreLocation
final class LocationViewViewModel: ObservableObject {
static let previewWeather: Response = load("Weather.json")
let weatherManager = WeatherManager()
let locationManager = LocationManager.shared
@Published var weather: Response
init(weather: Response) { // Remove async
DispatchQueue.main.async { // Here, you enter in an async environment
let data = await fetchData() // Read the data and pass it to a constant
DispatchQueue.main.async { // Get on the main thread
self.weather = data // Here, change the state of you app
}
}
}
func fetchData() async -> Response {
guard let weather = try? await weatherManager.getWeather(latitude: weatherManager.latitude!, longitude: weatherManager.latitude!) else { fatalError("Network Error.") }
return weather
}
var city: String {
return locationManager.getCityName()
}
var date: String {
return dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(weather.current.dt)))
}
var weatherIcon: String {
if weather.current.weather.count > 0 {
return weather.current.weather[0].icon
}
return "sun.max"
}
var temperature: String {
return getTempFor(temp: weather.current.temp)
}
var condition: String {
if weather.current.weather.count > 0 {
return weather.current.weather[0].main
}
return ""
}
var windSpeed: String {
return String(format: "%0.1f", weather.current.wind_speed)
}
var humidity: String {
return String(format: "%d%%", weather.current.humidity)
}
var rainChances: String {
return String(format: "%0.0f%%", weather.current.dew_point)
}
var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
var dayFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "EEE"
return formatter
}()
var timeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "hh a"
return formatter
}()
func getTimeFor(time: Int) -> String {
return timeFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(time)))
}
func getTempFor(temp: Double) -> String {
return String(format: "%0.1f", temp)
}
func getDayFor(day: Int) -> String {
return dayFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(day)))
}
}
Also i fetched that data for my previous view in my weather manager so im using the same function in my viewModel. My weatherManager;
final class WeatherManager {
var longitude = LocationManager.shared.location?.coordinate.longitude
var latitude = LocationManager.shared.location?.coordinate.latitude
var units: String = "metric"
func getWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> Response {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/onecall?lat=\(latitude)&lon=\(longitude)&units=\(units)&exclude=hourly,minutely&appid=\(API.API_KEY)") else { fatalError("Invalid Url.")}
let urlRequest = URLRequest(url: url)
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data") }
let decodedData = try JSONDecoder().decode(Response.self, from: data)
return decodedData
}
}
But I stuck with compile errors about initializing my weather Also tried to make my weather model optional but in the end i get the fatal error which says Fatal error: Unexpectedly found nil while unwrapping an Optional value What is the correct way of doing this if you are using fetched data in many views & viewModels
Your init()
is trying to run asynchronously and it's updating a @Published
property. Even if you manage to avoid compile errors, you cannot update a property that will change the state of your views (@Published
) unless you are on the main thread.
What I propose:
@Published var weather = Response() // Initialise this property in some way, the dummy values will be used by the app until you complete fetching the data
init(weather: Response) { // Remove async
Task { // Here, you enter in an async environment
let data = await fetchData() // Read the data and pass it to a constant
DispatchQueue.main.async { // Get on the main thread
self.weather = data // Here, change the state of you app
}
}
}
I hope this works, but it would be better if after "But I stuck with compile errors..." you showed what kind of errors you find. I tried to use my best guess with the solution above.