1

I need to implement WeatherKit into a macOS widget. After receiving the current weather based on your current location, the app converts currentWeather.condition to one of four strings, where the app picks an image depending on said string.

I've messed around with a heap of different code placements and variations, but nothing's worked. Nothing is reflected in the image view, and there's nothing coming out of the console when I put a simple print line in the fetchLocation block. I also thought it could have been an issue with receiving the current location, so I put in placeholder coordinates but the same issue occurs.

Could you guys help me find the right code and placements to get this working?

WeatherKit is working in the main app, and the WeatherKit capability is enabled on the widget target.

This is the widget so far (excluding irrelevant body stuff):

import WidgetKit
import WeatherKit
import SwiftUI
import CoreLocation
import Intents

struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent(), weatherString: "sunny")
    }

func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
    let entry = SimpleEntry(date: Date(), configuration: configuration, weatherString: "sunny")
    completion(entry)
}

var widgetLocationManager = WidgetLocationManager()

func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    Task {
    var entries: [SimpleEntry] = []
    
//        var widgetLocationManager = WidgetLocationManager()
        let service = WeatherService()
        
//            widgetLocationManager.fetchLocation(handler: { location in
                do {
                    var weatherString = "sunny"
                    let weather = try await service.weather(for: CLLocation(latitude: 40.7128, longitude: -74.0060))

                switch weather.currentWeather.condition {
                case .clear, .mostlyClear, .partlyCloudy: weatherString = "snowy"
                case .cloudy, .blowingDust, .foggy, .haze, .mostlyCloudy, .smoky: weatherString = "cloudy"
                case .hot, .frigid:
                    if weather.currentWeather.cloudCover < 0.5 {
                        weatherString = "sunny"
                    } else {
                        weatherString = "cloudy"
                    }
                case .drizzle, .heavyRain, .isolatedThunderstorms, .rain, .sunShowers, .scatteredThunderstorms, .strongStorms, .thunderstorms, .hail, .hurricane, .tropicalStorm: weatherString = "rainy"
                case .flurries, .sleet, .snow, .sunFlurries, .wintryMix, .blizzard, .blowingSnow, .freezingRain, .freezingDrizzle, .heavySnow: weatherString = "snowy"
                default: break
                }
                
                let currentDate = Date()
                for hourOffset in 0 ..< 5 {
                    let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
                    let entry = SimpleEntry(date: entryDate, configuration: configuration, weatherString: weatherString)
                    entries.append(entry)
                }

                let timeline = Timeline(entries: entries, policy: .atEnd)
                completion(timeline)

            } catch {
                assertionFailure(error.localizedDescription)
            }
            }
//            })

    // Generate a timeline consisting of five entries an hour apart, starting from the current date.
    
}
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
    let weatherString: String
}

struct Clock_WidgetEntryView: View {
var entry: Provider.Entry

var body: some View {
    
    VStack {
        Image(entry.weatherString)
            .resizable()
            .aspectRatio(contentMode: .fit)
    }.background(Color.black)
    
}
}

class WidgetLocationManager: NSObject, CLLocationManagerDelegate {
    var locationManager: CLLocationManager?
    private var handler: ((CLLocation) -> Void)?

override init() {
    super.init()
    DispatchQueue.main.async {
        self.locationManager = CLLocationManager()
        self.locationManager!.delegate = self
        if self.locationManager!.authorizationStatus == .notDetermined {
            self.locationManager!.requestWhenInUseAuthorization()
        }
    }
}

func fetchLocation(handler: @escaping (CLLocation) -> Void) {
    self.handler = handler
    self.locationManager!.requestLocation()
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    self.handler!(locations.last!)
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    print(error)
}
}

@main
struct Clock_Widget: Widget {
    let kind: String = "Clock_Widget"

var body: some WidgetConfiguration {
    IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
        Clock_WidgetEntryView(entry: entry)
    }
    .configurationDisplayName("My Widget")
    .description("This is an example widget.")
}
}

struct Clock_Widget_Previews: PreviewProvider {
    static var previews: some View {
        Clock_WidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), weatherString: "sunny"))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}
HangarRash
  • 7,314
  • 5
  • 5
  • 32
David
  • 87
  • 12
  • Xcode 14 has a known issue where the widget extension debugger is not automatically attached. You need to manually add it. Refer https://stackoverflow.com/a/74145087. This would make breakpoints work. For logging use Apple's Logger framework to log and view the Console app for logs – user1046037 Dec 12 '22 at 00:29
  • Did you find an answer to your problem? I'm also having the same problem. CoreLocation (user location, geocoding, etc) is working fine. But anytime I try to use the async await version of the WetherKit to get the current weather, the widget goes blank. But that's only in the device! In the simulator is working fine... – Dan Flict Jan 25 '23 at 02:06
  • 1
    @DanFlict Unfortunately I wasn't able to get it working and gave up in favour of another Weather API. – David Jan 25 '23 at 03:05
  • Does this answer your question? [How do I use WeatherKit Swift framework to fetch the weather inside a Widget Extension?](https://stackoverflow.com/questions/75236895/how-do-i-use-weatherkit-swift-framework-to-fetch-the-weather-inside-a-widget-ext) – lorem ipsum Jan 25 '23 at 18:23

0 Answers0