Search code examples
iosswiftcore-locationbeaconregion-monitoring

Starting locations updates after enter beacon region monitored Swift


I'm making a ibeacon region monitoring app with location updates when the user enter into this region (app not in foreground). This location updates must be configured as kCLLocationAccuracyBestForNavigation accuracy (because I have to make a tracking while the use remain in the region, subscribe to me significant changes is not enough). Everything works well, but after 20 seconds (sometimes 1 minute o more) I stop receiving locations updates. I put all the keys in info.plist for always location usage, I include the background modes in capabilities section and locations updates on background.

I configure the locationManager with different configurations and always the SO stops my locations updates. I'm using IOS 12 and Iphone 7 for testing.

The way I configure CLLocationManager:

self.locationManager.desiredAccuracy
=kCLLocationAccuracyBestForNavigation
self.locationManager.activityType = .automotiveNavigation
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.delegate = self
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.pausesLocationUpdatesAutomatically = false

Start location updates (when user enter in Ibeacon Region):

func beaconManager(_ manager: KTKBeaconManager, didEnter region: 
KTKBeaconRegion) {



    self.locationManager.startUpdatingLocation()


}

And finally, in didUpdate locations i call a web service:

func locationManager(_ manager: CLLocationManager, 
didUpdateLocations locations: [CLLocation]) {

    //Call web service using alamofire

}

I ask for your help to know if I am performing the settings correctly for the purpose I want to perform and any clue that lets me know why the operating system kills my process to get locations updates


Solution

  • Getting regular location updates in the background on iOS is tricky. The operating system is designed to keep apps from constantly running in the background to optimize battery usage, and it suspends them after a period of time unless you have several things exactly right.

    You need to do three things:

    1. You must get obtain always location permission from the user (as you say you've done).

    2. You must add the following entry to your Info.plist. This will allow your app to run indefinitely in the background, however if you wish to submit your app to the App Store, this entry will also declare to reviewers that it is a background location app, and you will need to convince them that it provides a location-based benefit to the user, and that the user is aware of this benefit.

     <key>UIBackgroundModes</key>
     <array>
         <string>location</string>
     </array>
    
    1. You must maintain a background thread to keep your app alive. It doesn't matter if you actually do anything in this background thread. Just having it be active keeps iOS from suspending your app.
      var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
    
      func extendBackgroundRunningTime() {
        if (backgroundTask != UIBackgroundTaskIdentifier.invalid) {
          // already started
          return
        }
        NSLog("Attempting to extend background running time")
    
        self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
          NSLog("Background task expired by iOS.  Cannot run again until a new beacon region event")
          UIApplication.shared.endBackgroundTask(self.backgroundTask)
          self.backgroundTask = UIBackgroundTaskIdentifier.invalid
        })
    
        DispatchQueue.global().async {
          while (true) {
            let backgroundTimeRemaining = UIApplication.shared.backgroundTimeRemaining
            // This will be a very large number if you have proper permissions
            // If not, it will generally count down from 10 seconds once you are in the
            // background until iOS suspends your app.
            NSLog("Thread background time remaining: \(backgroundTimeRemaining)")
            Thread.sleep(forTimeInterval: 1.0)
          }
        }
      }