Continuous location tracking ios after N minutes

62 Views Asked by At

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)
    }
}
1

There are 1 best solutions below

5
Paulw11 On

You can't do it. At least not with a background refresh task. App refresh tasks will be executed when iOS chooses. The earliestBeginDate is 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:

  1. Use startUpdatingLocation with "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.

  2. 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).

  3. 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.