3

I'm looking for a way that I can track that a user has arrived near a designated set of co-ordinates. The functionality needs to work while the application is in the background (preferably within 100 metres). Also, to preserve the battery, I ideally do not want to get too many co-ordinate readings (perhaps a reading every 10 minutes for no longer than a couple of hours).

There are a couple of ways that I have tried to accomplish this task, but have been unable to obtain the desired result:

Background Timer:

I had added a background task in (App.delegate)

func applicationDidEnterBackground(_ application: UIApplication)

Which executed a repeated Timer.scheduledTimer to get co-ordinates and process

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

to detect if the user was within range. This method worked if applied in the short-term, but only until the application was suspended, which was about 3 minutes. Ideally, I would not want to get co-ordinates this frequently.

Region Monitoring:

I had initialised the CLLocationManager as shown below:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    locationManager.delegate = self
    locationManager.allowsBackgroundLocationUpdates = true
    locationManager.pausesLocationUpdatesAutomatically = false
    locationManager.activityType = .otherNavigation
    locationManager.requestAlwaysAuthorization()
}

The LocationManager starts when the application enters into the background:

func applicationDidEnterBackground(_ application: UIApplication) {
    self.monitorRegionAtLocation(center: CLLocationCoordinate2D(latitude: x, longitude: y), identifier: id)
    locationManager.startUpdatingLocation()
}

Code for monitoring of region:

func monitorRegionAtLocation(center: CLLocationCoordinate2D, identifier: String ) {
    // Make sure the app is authorized.
    if CLLocationManager.authorizationStatus() == .authorizedAlways {
        // Make sure region monitoring is supported.
        if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
            // Register the region.
            let maxDistance = 200.0
            let region = CLCircularRegion(center: center,
                                          radius: maxDistance, identifier: identifier)
            region.notifyOnEntry = true
            region.notifyOnExit = false

            locationManager.startMonitoring(for: region)
        }
    }
}

And I added a didEnterRegion function block for CLLocationManager:

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
    if let region = region as? CLCircularRegion {
        let identifier = region.identifier
        print("FOUND: " + identifier)
    }
}

The code appears to work for detecting entry into a region, however the co-ordinates are not updating while in the background.

Additional Information

  • I have the Background Modes of Location Updates and Background Fetch enabled
  • I have supplied values for 'Location Always Usage Description' and 'Location When in Use Usage Description' in the Info.plist
  • The App Settings shows 'Always' permission against the Location

I believe that there has to be a better way of operating these kinds of checks in the background, but I haven't discovered any method of detecting other movements in the background.

Any direction on this matter would be greatly appreciated, and if you need any more information, please let me know and I'll provide what I can.

UPDATE:

I have modified the approach following the advice of comments below to use Region Monitoring.

Matthew Spencer
  • 2,265
  • 1
  • 23
  • 28
  • 1
    [Use region monitoring](https://developer.apple.com/documentation/corelocation/monitoring_the_user_s_proximity_to_geographic_regions) – Paulw11 Sep 25 '17 at 05:16
  • Thanks @Paulw11. I have modified the approach and the question to better suit Region Monitoring, however I have been unable to get the co-ordinates in from the background. – Matthew Spencer Sep 25 '17 at 06:22
  • where did you initialize your `locationManager`? It's local variable or class property? – Taras Chernyshenko Sep 27 '17 at 14:46
  • Do you want to get co-ordinates in Killed or Suspended mode? I have recently completed a project about geofencing, and found that it works only when app is in background, I will update you tomorrow with the things I have done. Would be happy if I be helpful to you. :) – Nikhil Manapure Sep 27 '17 at 17:59
  • @TarasChernyshenko I currently have it initialised in AppDelegate. When Originally designing the Background Timer solution, I had it contained in a separate BackgroundWorker class, which seemed to work until the app was suspended after 3 minutes. – Matthew Spencer Sep 28 '17 at 00:50
  • @NikhilManapure Thanks. I would like co-ordinates while it is running in suspended state, but only for up to an hour. If the App is killed, the co-ordinates shouldn't be necessary. I am trying to save on battery power and take as few readings as possible through this time. – Matthew Spencer Sep 28 '17 at 00:56

2 Answers2

4

Any location update/monitoring requires it's location manager to be configured properly so that it can work to the best to provide the desired location update. It's important to check some point when doing background location update:

1. Check background modes of location updates and background fetch should be enable

2. Check 'Location Always Usage Description' and 'Location When in Use Usage Description' in the Info.plist should be provided

3. Check if you want to pause in between location update - if yes then you need to provide activity type so that location manager can determine best way to pause location update for you

4. Check if you want to apply distance filter - you want user(device) to move some minimum amount for location manager to send updated location

5. Check if you want desired accuracy- This may cause power drain for certain accuracy type

In your code I can see location manager is configured with some of the parameter but missing accuracy and distance filter for background mode.

   locationManager.allowsBackgroundLocationUpdates = true
   locationManager.pausesLocationUpdatesAutomatically = false
   locationManager.activityType = .otherNavigation

Also, if you see pause location update property in Apple doc it says:

For apps that have in-use authorization, a pause to location updates ends access to location changes until the app is launched again and able to restart those updates. If you do not wish location updates to stop entirely, consider disabling this property and changing location accuracy to kCLLocationAccuracyThreeKilometers when your app moves to the background. Doing so allows you to continue receiving location updates in a power-friendly manner.

Essentially it tells that if you want disable pause then you have to keep accuracy level (kCLLocationAccuracyThreeKilometers). Which I guess is missing in your approach.

Also, you can check this link which actually starts background task and then starts location manager monitoring inside the background task. Hope it helps.

manismku
  • 2,160
  • 14
  • 24
  • Thanks for your feedback. I had run a few tests over the weekend with the region monitoring and was able to establish some didEnterRegion triggers from the background. I don't believe accuracy or distance filter is required when using locationManager.startMonitoringSignificantLocationChanges(), which I opted to use in the end. [This resource](https://www.raywenderlich.com/136165/core-location-geofencing-tutorial) was a very useful guide for getting to the bottom of my issue, but your points are also important. – Matthew Spencer Oct 01 '17 at 23:01
  • Ok. Great. So what was the actual issue? – manismku Oct 02 '17 at 03:32
0

The question says "CLLocationManager Region Monitoring: Detect Arrival in Background". And this is very much possible, but detecting anything after being killed is not possible (from iOS 7).

Whenever user swipe ups your app app-switcher, iOS takes it as the user doesn't wish the app to be running in the background, and so all the call-backs are stopped.

This answer, this answer and this answer also says the same thing. However Apple doc is a little confusing.

My personal observation is that app gets called even in Killed mode but very rarely.

And about getting the location, whenever the delegate method of geofencing is called, you can get location easily.

And the background modes are really not needed for your requirement.

And unfortunately (fortunately for iOS user as they save battery) we don't really have a way to get location just for 1 hr after app being killed.

Nikhil Manapure
  • 3,748
  • 2
  • 30
  • 55
  • Thanks for your comment. Perhaps I am confusing the issue as I stated the 'background', but what I mean is in Suspended State (I've fixed this in title). It seems that the solution will work for around 3 minutes, which is likely the Background State, but then stops running after that, which I suspect is due to the app going into Suspended State. The app is still in the App-Switcher and is not being swiped up to kill it (Just sitting in the background). – Matthew Spencer Sep 28 '17 at 06:39