Widget中使用WeatherKit获取天气数据失败求助
Widget中WeatherKit无法获取天气数据的排查与解决
问题背景
主应用使用WeatherKit获取天气正常,但Widget完全无法获取数据,调试器无日志输出。已完成以下配置:
- 主应用和Widget的App ID均启用WeatherKit
- 两个Target都添加了WeatherKit能力
- Widget的Info.plist中启用了
Widget Wants Location
核心代码展示
Widget的Timeline Provider代码
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { Task { await viewModel.getCurrentWeather() // 尝试获取数据 var entries: [SimpleEntry] = [] // 生成5个间隔1分钟的时间线条目 let currentDate = Date() for hourOffset in 0 ..< 5 { print(viewModel.locationManager.locationName) let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, weather: viewModel.currentWeather) entries.append(entry) } let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) print(completion) } } struct SimpleEntry: TimelineEntry { let date: Date let weather: Weather } struct Forecast_WidgetsEntryView : View { var entry: Provider.Entry @Environment(\.widgetFamily) var type var body: some View { DailyForecast1(weather: entry.weather) } }
WeatherViewModel代码
@MainActor class WeatherViewModel: ObservableObject { let locationManager = LocationManager() @Published var currentWeather: Weather = .empty() @Published var isLoading = true init() { } func getCurrentWeather() async { print("fetching data...") isLoading = true do { guard let currentLocation = locationManager.lastLocation else { return } let weather = try await WeatherService.shared.weather(for: currentLocation) self.currentWeather = Weather(temperature: weather.currentWeather.temperature.value, condition: weather.currentWeather.condition.description.capitalized, symbolName: weather.currentWeather.symbolName, hightTemp: weather.dailyForecast.first?.highTemperature.value ?? 0.0, lowTemp: weather.dailyForecast.first?.lowTemperature.value ?? 0.0, dailyWeathers: weather.dailyForecast.forecast, hourlyWeathers: weather.hourlyForecast.forecast, daylight: weather.currentWeather.isDaylight) isLoading = false } catch { isLoading = false print(error.localizedDescription) } } }
问题分析与解决方案
1. 主线程约束导致异步任务阻塞
WeatherViewModel标记了@MainActor,但Widget的getTimeline运行在后台队列,强制调度到主线程会导致任务无法正常执行。
修复方案:
移除@MainActor约束,仅在更新@Published属性时切换到主线程:
class WeatherViewModel: ObservableObject { let locationManager = LocationManager() @Published var currentWeather: Weather = .empty() @Published var isLoading = true init() { } func getCurrentWeather() async { print("fetching data...") isLoading = true do { guard let currentLocation = locationManager.lastLocation else { print("Widget: 无法获取当前位置") await MainActor.run { self.isLoading = false } return } let weather = try await WeatherService.shared.weather(for: currentLocation) await MainActor.run { self.currentWeather = Weather(temperature: weather.currentWeather.temperature.value, condition: weather.currentWeather.condition.description.capitalized, symbolName: weather.currentWeather.symbolName, hightTemp: weather.dailyForecast.first?.highTemperature.value ?? 0.0, lowTemp: weather.dailyForecast.first?.lowTemperature.value ?? 0.0, dailyWeathers: weather.dailyForecast.forecast, hourlyWeathers: weather.hourlyForecast.forecast, daylight: weather.currentWeather.isDaylight) self.isLoading = false } } catch { await MainActor.run { self.isLoading = false } print("Widget获取天气失败: \(error.localizedDescription)") } } }
2. Widget位置权限未正确适配
Widget的位置权限逻辑与主应用不同,locationManager.lastLocation可能始终为nil,导致获取天气的代码直接返回。
修复方案:
- 在Widget的Info.plist中添加位置权限描述(如
NSLocationWhenInUseUsageDescription) - 修改
LocationManager,在Widget环境下主动请求位置:
class LocationManager: NSObject, CLLocationManagerDelegate { var lastLocation: CLLocation? private let manager = CLLocationManager() override init() { super.init() manager.delegate = self // 判断是否为Widget扩展 if Bundle.main.bundlePath.hasSuffix(".appex") { manager.requestWhenInUseAuthorization() manager.requestLocation() // 主动触发一次位置获取 } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { lastLocation = locations.first } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("位置获取失败: \(error.localizedDescription)") } }
3. Widget日志输出未配置
Widget的调试日志默认不会显示在Xcode主控制台,需要手动选择Widget进程:
在Xcode控制台的Process下拉菜单中,选择对应的Widget扩展进程,即可查看日志。
4. 优化Timeline刷新逻辑
当天气获取失败时,设置更短的刷新间隔,避免一直显示空数据:
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { Task { await viewModel.getCurrentWeather() var entries: [SimpleEntry] = [] let currentDate = Date() for hourOffset in 0 ..< 5 { let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, weather: viewModel.currentWeather) entries.append(entry) } // 若天气为空,1分钟后重新尝试刷新 let reloadPolicy: TimelineReloadPolicy = viewModel.currentWeather == .empty() ? .after(Date().addingTimeInterval(60)) : .atEnd let timeline = Timeline(entries: entries, policy: reloadPolicy) completion(timeline) } }
内容的提问来源于stack exchange,提问作者Mc.Lover
相关产品推荐
相关产品推荐

