You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 01:50:19