Implementing WeatherKit to widget - SwiftUI

382 views Asked by At

So I have a widget for a weather app (the app currently doesn't use WeatherKit but I'd eventually like to move it over), but I want to implement a widget using WeatherKit beforehand. I made a practice app using WeatherKit and got all the components I'd want to use in the widget to work correctly.

Here's the entirety of the main app that works perfectly:


import SwiftUI
import CoreLocation
import WeatherKit

var userCity: String  = ""


class LocationManager: NSObject, ObservableObject {
    @Published var currentLocation: CLLocation?
    @Published var userCity: String = ""
    private let locationManager = CLLocationManager()

    override init() {
        super.init()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = kCLDistanceFilterNone
        locationManager.requestAlwaysAuthorization()
        locationManager.startUpdatingLocation()
        locationManager.delegate = self
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last, currentLocation == nil else { return }
        
        
        DispatchQueue.main.async {
            self.currentLocation = location
        }
        
        
        CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
            print(location)
            guard error == nil else {
                print("Reverse geocoder failed with error" + error!.localizedDescription)
                return
            }
            guard placemarks!.count > 0 else {
                print("Problem with the data received from geocoder")
                return
            }
            let pm = placemarks![0].locality
            print(pm!)
            let userLocation = (pm!)
            print(userLocation)
            
            
            DispatchQueue.main.async {
            self.userCity = userLocation
            }
            
        })
    }
}

struct ContentView: View {
    
    let weatherService = WeatherService.shared
    
    @StateObject private var locationManager = LocationManager()
    @State private var weather: Weather?
    

    var body: some View {
     
        VStack {
            if let weather{
            
                
                let celsiusWeather = weather.currentWeather.temperature.converted(to: .celsius)
                
                let celsiusFormatted = String(format: "%.0f", celsiusWeather.value)
                
                let celsiusFinal = "\(celsiusFormatted)°C"
                
                let fahrenheitWeather = weather.currentWeather.temperature.converted(to: .fahrenheit)
                
                let fahrenheitFormatted = String(format: "%.0f", fahrenheitWeather.value)
                
                let fahrenheitFinal = "\(fahrenheitFormatted)°F"
                
                
                VStack{
                    Image(systemName: weather.currentWeather.symbolName)
                        .resizable()
                        .symbolRenderingMode(.palette)
                        .scaledToFit()
                        .frame(width: 80, height: 80)
                    Text(locationManager.userCity)
                        .font(.largeTitle)
                    Text(fahrenheitFinal)
                    Text(celsiusFinal)
                   
                }
            }
        }
        .padding()
        .task(id: locationManager.currentLocation) {
            do{
                if let location = locationManager.currentLocation{
                    self.weather = try await weatherService.weather(for: location)
                    print(weather!)
                }
                
            }catch{
                print(error)
            }
        }
        
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Basically, I've just been trying to move this code over to the widget, unsure if that would even work properly (clearly something doesn't).

And here is what I've been trying to do with the widget, but it comes up blank, so something is coming up nil and can't retrieve the weather data. But Xcode doesn't give me any errors.

import WidgetKit
import SwiftUI
import WeatherKit
import CoreLocation

var userCity: String?


class LocationManager: NSObject, ObservableObject {
    @Published var currentLocation: CLLocation?
    @Published var userCity: String = ""
    private let locationManager = CLLocationManager()

    override init() {
        super.init()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = kCLDistanceFilterNone
        locationManager.requestAlwaysAuthorization()
        locationManager.startUpdatingLocation()
        locationManager.delegate = self
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last, currentLocation == nil else { return }
        
        
        DispatchQueue.main.async {
            self.currentLocation = location
        }
        
        
        CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
            print(location)
            guard error == nil else {
                print("Reverse geocoder failed with error" + error!.localizedDescription)
                return
            }
            guard placemarks!.count > 0 else {
                print("Problem with the data received from geocoder")
                return
            }
            let pm = placemarks![0].locality
            print(pm!)
            let userLocation = (pm!)
            print(userLocation)
            
            
            DispatchQueue.main.async {
                self.userCity = userLocation
            }
            
        })
    }
}

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), city: "", temp: "0") // placeholder humidity
    }

    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), city: "", temp: "0") // placeholder humidity
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        
        let locationManager = LocationManager()
        @State var currentWeather: Weather?
        
        
        let nextUpdate = Date().addingTimeInterval(1800)
        
        Task {

            do{
                if let location = locationManager.currentLocation{
                    let currentWeather = try await WeatherService.shared.weather(for: location)
                    print(currentWeather)
                }

            }catch{
                print(error)
            }
        
        }
        
        let entry = SimpleEntry(date: .now, city: userCity!, temp: (currentWeather?.currentWeather.temperature.description)!)
        
        let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let city: String
    let temp: String
}

struct WidgetWeatherTestWidgetEntryView : View {
   
    
    var entry: Provider.Entry

    var body: some View {
        VStack{
            Text(entry.city)
            Text(entry.temp)
        }
    }
}

struct ShitsdagWidget: 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([.systemSmall])
    }
}

Because widgets are set up slightly differently than just SwiftUI with the timeline and everything, I might just have things in the wrong spot to be triggered in the right order. Any help would be great.

0

There are 0 answers