How to run .task() at least once every hour

120 views Asked by At

In my main view MainView() I load weather data from WeatherKit asynchronously via .task() which runs whenever the user's location changes:

.task(id: locationManager.currentLocation){

      if let location = locationManager.currentLocation{
           weather = try await weatherService.weather(for: location)
      }
}

However, the user's location may not change for a long period of time or even never and I would like for the weather data to be at least updated once every hour.

How would I update WeatherKit data every hour AND update every single time their location changes.

Obviously I can’t rely on the WeatherKit weather object within the ‘.task(id:)’ to update as that is the object that I’d need to update.

1

There are 1 answers

2
Noor Ahmed Natali On

for fetching location after every few mins what approach I have used is

class BackgroundLocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    
    static let shared = BackgroundLocationManager()
    private var locationManager: CLLocationManager!
    private var timer: DispatchSourceTimer?
    private var counter: Int = 0
    @Published var location: LocationModel? = nil
    
    private override init() {
        super.init()
        locationManager = CLLocationManager()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = kCLDistanceFilterNone
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.requestAlwaysAuthorization()
    }
    
    
    private func startTimer(delay: Int = 15) {
        let queue = DispatchQueue(label: "com.example.timer", qos: .background)
        timer = DispatchSource.makeTimerSource(queue: queue)
        timer?.schedule(deadline: .now(), repeating: .seconds(delay))
        timer?.setEventHandler { [weak self] in
                self?.locationManager.startUpdatingLocation()
            self?.counter = 0
        }
        timer?.resume()
    }
    
    func fetchLocation(interval: Int? = nil) {
        if let interval = interval {
            startTimer(delay: interval)
        } else {
            self.locationManager.startUpdatingLocation()
        }
        
    }
    
    
    func stopFetchingLocation() {
        timer?.cancel()
        timer = nil
    }
    
    
    
    
}

extension BackgroundLocationManager {
    
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .notDetermined:
            break
        case .restricted, .denied:
          // do something when permission is denied
        case .authorizedAlways, .authorizedWhenInUse, .authorized:
         // do something when permission is given
            break
        @unknown default:
            return
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        if let error = error as? CLError, error.code == .denied {
            // do something when unable to fetch location
            return
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        self.counter += 1
        locationManager.stopUpdatingLocation()
        let currentLocation = locations.last?.coordinate
        let long = String(currentLocation?.longitude ?? 0)
        let lat = String(currentLocation?.latitude ?? 0)
        let verticalAcc = String(locations.last?.verticalAccuracy ?? 0)
        let horizontalAcc = String(locations.last?.horizontalAccuracy ?? 0)
        let altitude = String(locations.last?.altitude ?? 0)
        let location = LocationModel(longitude: long, latitude: lat,
                                     verticalAccuracy: verticalAcc,
                                     horizontalAccuracy: horizontalAcc, alitude: altitude)
        if counter <= 1 {
            self.location = location
        }
    }
    
}

To use it according to my need

struct MainView: View {
    @StateObject var vm = MainViewModel()

    init() {
        BackgroundLocationManager.shared.fetchLocation() // when needed once
        BackgroundLocationManager.shared.fetchLocation(interval: 15) // needed after how many seconds
    }

    var body: some View {
        ZStack {
          MyCustomeView()
        }
        .onReceive(BackgroundLocationManager.shared.$location) { location in
            vm.doSomething(location: location)
        }
    }
}