iBeacon: didDetermineState never called when exiting a region

Viewed 226

I'm trying to monitoring region and detecting beacons when the app is killed (in foreground everything works fine). I've read that it should be sufficient to set allowsBackgroundLocationUpdates=true and pausesLocationUpdatesAutomatically=false to wake up the app, but only if an enter/exit event is detected.

Now, my problem is that when I turn off the beacon, didDetermineState and didExitRegion are never called. And if I request the state explicitly, it return that it is still inside the region. What am I missing?

This is my code, entirely in the AppDelegate.

func requestLocationPermissions() {
        locationManager = CLLocationManager()
        locationManager.delegate = self
        locationManager.requestAlwaysAuthorization()
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.pausesLocationUpdatesAutomatically = false
        
        startMonitoring()
}

func startMonitoring() {
        let constraint = CLBeaconIdentityConstraint(uuid: Config.Beacons.uuid, major: Config.Beacons.major, minor: Config.Beacons.minor)
        let beaconRegion = CLBeaconRegion(beaconIdentityConstraint: constraint, identifier: Config.Beacons.beaconID)
        beaconRegion.notifyEntryStateOnDisplay = true

        locationManager.startMonitoring(for: beaconRegion)        
        locationManager.startRangingBeacons(satisfying: constraint)
}

func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
        if state == .inside {
            print("AppDelegate: inside beacon region")
        } else {
            print("AppDelegate: outside beacon region")
        }
}
    
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        print("AppDelegate: entered region")
}
    
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        print("AppDelegate: exited region")
}
    
func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon], satisfying beaconConstraint: CLBeaconIdentityConstraint) {
        guard beacons.count > 0 else {
            let constraint = CLBeaconIdentityConstraint(uuid: Config.Beacons.uuid, major: Config.Beacons.major, minor: Config.Beacons.minor)
            locationManager.requestState(for: CLBeaconRegion(beaconIdentityConstraint: constraint, identifier: Config.Beacons.beaconID))
            return
        }
        
        // Other stuff
 }
1 Answers

A few tips:

  1. You must request and obtain “always” location permission from the user in order to get a region exit callback.
  2. If the above is granted, the region exit callback should happen 30 seconds after the beacon stops transmitting.
  3. set locationManager.notifyEntryStateOnDisplay=true which will give you an extra 10 seconds of background scanning when the display is illuminated.
  4. If you have trouble with the above, turn on beacon ranging and log the number of beacons that are detected each second for your beacon region. This will tell you if iOS truly believes the beacon is still being seen. If you have the setting from (3) enabled, each time you illuminate the display you should get 10 secs of ranging callbacks.
  5. If you stop getting ranging callbacks entirely, this may indicate a permissions issue.

Make sure you really do have always location permission granted and not just “while using” permission.

Related