I can't fetch the weather using the WeatherKit framework inside a Widget Extension.
Here's a project I created just for this example. This widget shows the humidity of a sample location.
Here's the code:
import WidgetKit
import SwiftUI
import WeatherKit
import CoreLocation
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), humidity: 9.99) // placeholder humidity
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), humidity: 9.99) // placeholder humidity
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
Task {
let nextUpdate = Date().addingTimeInterval(3600) // 1 hour in seconds
let sampleLocation = CLLocation(latitude: 51.5072, longitude: 0.1276) // sample location (London)
let weather = try await WeatherService.shared.weather(for: sampleLocation)
let entry = SimpleEntry(date: .now, humidity: weather.currentWeather.humidity)
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let humidity: Double
}
struct WidgetWeatherTestWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Text("\(entry.humidity)")
}
}
struct WidgetWeatherTestWidget: Widget {
let kind: String = "WidgetWeatherTest"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WidgetWeatherTestWidgetEntryView(entry: entry)
}
.configurationDisplayName("Widget Weather Test")
.description("Testing weatherkit in a Widget")
.supportedFamilies([.systemMedium])
}
}
It works fine in the simulator:
But it doesn't work in a real device. As soon as I call WeatherService
's weather(for: CLLocation) async throws -> Weather
the widget get's "blocked".
I've tried moving the try await WeatherService.shared.weather(for: sampleLocation)
to different places, but nothing worked. As soon as I comment out this line (and create an entry with a placeholder humidity) everything works.
I added the WeatherKit capability in the main app target and in the WidgetExtension target. I also added the WeatherKit "Capability" and "App Service" in the "Certificates, Identifiers & Profiles" of the Apple Developer website.
The WeatherKit framework works fine in other parts of the main app.
I haven't tried using the WeatherKit REST API, yet.
Any ideas what could be happening?
CodePudding user response:
You should set your entry to use Result
, that way you can visualize any errors that are happening.
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
let result: Result<any CurrentWeatherProtocol, Error>
}
Then in the View you can either present the error or the weather view.
struct PWCWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Text(entry.date, style: .time)
switch entry.result{
case .success(let weather):
Image(systemName: weather.symbolName)
case .failure(let error):
Text(error.localizedDescription)
let nserror = error as NSError
Text(nserror.userInfo.description)
Text("\(error)")
}
}
}
My whole code base is a part of a much more involved project but this is what the getTimeline
looks like.
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
Task{
let now = Date.now
let inHr = Calendar.current.date(byAdding: .hour, value: 1, to: now)!
do{
let location = try await locSvc.requestSingleLocation()
let wSvc = try await CDWeatherService().currentWeather(for: WeatherEntry(location: .init(location: location)))
//Return a successful result
let entry = SimpleEntry(date: now, configuration: configuration, result: .success(wSvc))
let timeline = Timeline(entries: [entry], policy: .after(inHr))
completion(timeline)
}catch{
//Return a failure result
let entry = SimpleEntry(date: now, configuration: configuration, result: .failure(error))
let timeline = Timeline(entries: [entry], policy: .after(inHr))
completion(timeline)
}
}
}
2 things that I found to be my issues are macOS and iOS Widget Extensions, you have to make sure you pick the right one.
Also, make sure you have added "WeatherKit" to the "Signing & Capabilities" of the Widget Extension.
WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors error 2.
Was related to not having Weather Kit included in "App Services" in AppStore Connect, it must be included in App Store Connect - Widget Identifier - Capabilities
App Store Connect - Widget Identifier - AppServices
Signing & Capabilities in Xcode for the App and/or Widget