WeatherKit symbol for single location

91 views Asked by At

I'm using WeatherKit in a project to display the SF Symbol for the weather in the app toolbar. But when I build and run the app on my device, Weather is always nil.

This is the WeatherManager class:

import Foundation
import WeatherKit

@MainActor class WeatherManager: ObservableObject {
    @Published var weather: Weather?
    
    public func getWeather() async {
        do {
            weather = try await Task.detached(priority: .userInitiated) {
                return try await WeatherService.shared.weather(for: .init(latitude: 28.3772, longitude: -81.5707))
            }.value
            
        } catch {
            fatalError("\(error)")
        }
    }
    
    var symbol: String {
        weather?.currentWeather.symbolName ?? "xmark.icloud.fill"
    }
    
    var temp: String {
        let temp =
        weather?.currentWeather.temperature
        
        let convert = temp?.converted(to: .fahrenheit).description
        return convert ?? ""
    }
}

And this how I use it in the SwiftUI View:

ToolbarItem(placement: .topBarLeading) {
    @ObservedObject var weatherManager = WeatherManager()

    Image(systemName: weatherManager.symbol)
        .task {
            await weatherManager.getWeather()
        }
}
1

There are 1 answers

0
Pierre Janineh On

The issue:

  1. You are executing an async task with weatherManager.getWeather().
  2. You are passing the weatherManager.symbol to Image before actual initialization of weather after the async task.

Fix: You could do one of the following:

  1. Create a state variable to handle changes and pass to Image:
@StateObject var weatherManager = WeatherManager()

@State private image = weatherManager.symbol

ToolbarItem(placement: .topBarLeading) {
    Image(systemName: image)
        .task {
            await weatherManager.getWeather()
        }
}
  1. As long as you are using an ObservableObject, with a published variable weather. Use it.
Image(systemName: 
   weatherManager
      .weather?
      .currentWeather
      .symbolName ??
   "xmark.icloud.fill")

Conclusion: When dealing with async tasks that return data, always make sure to handle the actual returned data. In your case, no data is handled after await getWeather(), and the symbol calculated property is never calculated after the first call (most probably, before the async task has finished). Also, make sure you use the correct property wrappers and learn the difference between @StateObject and @ObservedObject. This is a good guide.