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.
CodePudding user response:
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)
}
}
}
CodePudding user response:
Use a timer inside your view.
struct WeatherView: View {
let timer = Timer.publish(every: 3600, on: .main, in: .common).autoconnect()
var body: some View {
Text("Weather")
.onReceive(timer) { _ in
fetch()
}
}
func fetch() {
Task {
weather = try await weatherService.weather(for: location)
}
}
}