Has anyone implemented background location updates in iOS any help would be great below is my scenario
Problem Statement
- I want to Track user location after every N minutes and send it to server (both cases background and killed state)
What I have tried till now is
Enabled background modes, location updates, background fetch and background processing
Created a singleton for location tracking check the below code and calling this class on
didFinishLaunchingWithOptions - AppDelegate
Now I have xplored the BakcgroundTasks as well but the background task is registred but it is not executing after the given interval please check code for this as well below
class EmployeeAttendanceTracker: NSObject,CLLocationManagerDelegate {
static let shared = EmployeeAttendanceTracker()
private let locationManager = CLLocationManager()
private var lastLocationDate = Date()
static let LOCATION_INTERVAL = 1
var locationUpdate: (() -> Void)?
private override init() {
super.init()
setupLocationManager()
}
private func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = .other
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.showsBackgroundLocationIndicator = true
if #available(iOS 9.0, *) {
locationManager.requestAlwaysAuthorization()
} else {
locationManager.requestWhenInUseAuthorization()
}
}
//
//
// // MARK: - CLLocationManagerDelegate
//
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
//showLocationPermissionAlert()
Logger.s("Location access restricted.")
case .denied:
//showLocationPermissionAlert()
Logger.s("User denied location access.")
case .notDetermined:
// showLocationPermissionAlert()
Logger.s("Location access not determined.")
case .authorizedAlways:
if #available(iOS 9, *) {
locationManager.requestLocation()
} else {
locationManager.startUpdatingLocation()
locationManager.startMonitoringSignificantLocationChanges()
}
default:
// showLocationPermissionAlert()
break
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
Logger.s("User latitude: \(location.coordinate.latitude), longitude: \(location.coordinate.longitude)")
locationManager.stopUpdatingLocation()
let now = Date()
if isItTime(now: now as NSDate) {
if shouldSendLocationToServer() {
self.sendLocationToServer(location: location,completion: {
self.locationUpdate?()
})
}else{
self.locationUpdate?()
}
Logger.s(now)
Logger.s(location)
}else{
self.locationUpdate?()
}
}
AppDelegate.swift
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.example.location_update")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 minutes from now
do {
try BGTaskScheduler.shared.submit(request)
Logger.s("BG TASK REQUEST SUBMITTED")
} catch {
print("Unable to submit task request: \(error)")
}
}
func handleAppRefresh(task: BGAppRefreshTask) {
task.expirationHandler = {
// Handle expiration if needed
Logger.s("BG TASK EXPIRED")
task.setTaskCompleted(success: false)
}
Logger.s("HANDLE BG TASK REQUEST")
EmployeeAttendanceTracker.shared.locationUpdate = {
task.setTaskCompleted(success: true)
}
}
func registerBackGroundTasks() {
// Register for background tasks
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.location_update",
using: DispatchQueue.global()
) { task in
// Your task handler logic
//task.setTaskCompleted(success: true)
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
}
You can't do it. At least not with a background refresh task. App refresh tasks will be executed when iOS chooses. The
earliestBeginDateis exactly that; the earliest that the task will run. It may run minutes, hours or days after that date.There are a couple of strategies you can adopt:
Use
startUpdatingLocationwith "always" permission. This will deliver location updates about once per second. You can then report the location to your server every 'n' minutes. This will have significant battery impact and won't relaunch your app if it is terminated.Use significant location change monitoring. This will only deliver a location update if the user has moved about 500m. This may be longer than 'n' minutes if the user is walking or stops for a while or may be more frequently than 'n' minutes if the user is moving quickly (say in a car or train). Significant location monitoring uses much less power and will allow your app to be relaunched if it is terminated (only once a significant location change is detected).
Use a combination; regular location for frequent updates and significant location updates to relaunch if terminated.
Which approach you use depends on the use case of your app.
For an Uber style "driver" app you want continuous updates and it is reasonable to expect that the device is connected to external power.
For an Uber style "rider" app you want continuous updates but only for a short time period (from the start of the booking process until the pick up is made), so power drain probably isn't an issue.
For other use cases, consider whether significant location change on its own is sufficient.