24

I am having difficulties getting this to work for when the app is not running. I have locationManager:didRangeBeacons:inRegion: implemented and it is called when the app is running in the foreground or background, however it doesn't seem to do anything when I quit the app and lock the screen. The location services icon goes away and I never know that I entered a beacon range. Should the LocalNotification still work?

I have Location updates and Uses Bluetooth LE accessories selected in Background Modes (XCode 5) I didn't think I needed them.

Any help greatly appreciated.

-(void)watchForEvents { // this is called from application:didFinishLaunchingWithOptions
    id class = NSClassFromString(@"CLBeaconRegion");
    if (!class) {
        return;
    }

    CLBeaconRegion * rflBeacon = [[CLBeaconRegion alloc] initWithProximityUUID:kBeaconUUID identifier:kBeaconString];
    rflBeacon.notifyOnEntry = YES;
    rflBeacon.notifyOnExit = NO;
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    [self.locationManager startRangingBeaconsInRegion:rflBeacon];
    [self.locationManager startMonitoringForRegion:rflBeacon];
}

-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
    if (beacons.count == 0 || eventRanged) { // breakpoint set here for testing
        return;
    }

    eventRanged = YES;
    if (backgroundMode) { // this is set in the EnterBackground/Foreground delegate calls
        UILocalNotification *notification = [[UILocalNotification alloc] init];
        notification.alertBody = [NSString stringWithFormat:@"Welcome to the %@ event.",region.identifier];
        notification.soundName = UILocalNotificationDefaultSoundName;
        [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
    }

    // normal processing here...
}
Mani
  • 17,226
  • 13
  • 73
  • 97
Aaron Bratcher
  • 5,292
  • 2
  • 35
  • 64
  • "(I did have this working before and it would give a notification on the lock screen. Now that isn't working.)" ... what have you changed? – Mike Pollard Oct 02 '13 at 16:27
  • OK.. I did find out how to make my badge appear on the lock screen again when in background and question above has been updated: [self.locationManager startMonitoringForRegion:rflBeacon]; – Aaron Bratcher Oct 02 '13 at 16:40
  • This might help you: http://stackoverflow.com/questions/19127282/ibeacon-notification-when-the-app-is-not-running – random Oct 02 '13 at 20:15
  • I saw that when I was researching the issue. According to that answer, it should happen automatically. Apparently, it isn't so automatic for me :( – Aaron Bratcher Oct 02 '13 at 23:16
  • 1
    It should be fixed on iOS 7.1. Is it? http://beekn.net/2014/03/apple-ios-7-1-launches-major-ibeacon-improvement/ – atxe Mar 11 '14 at 21:59
  • instead of displaying a local notification is it possible to get data from webserver, let's say more information about the beacon, while the app is in background? – Onnmir Jun 06 '14 at 15:13

5 Answers5

11

Monitoring can launch an app that isn't running. Ranging cannot.

The key to having monitoring launch your app is to set this poorly documented flag on your CLBeaconRegion: region.notifyEntryStateOnDisplay = YES; This can launch your app on a region transition even after completely rebooting your phone. But there are a couple of caveats:

  1. Your app launches into the background only for a few seconds. (Try adding NSLog statements to applicationDidEnterBackground and other methods in your AppDelegate to see what is going on.)
  2. iOS can take its own sweet time to decide you entered a CLBeaconRegion. I have seen it take up to four minutes.

As far as ranging goes, even though you can't have ranging wake up your app, you can make your app do both monitoring and ranging simultaneously. If monitoring wakes up your app and puts it into the background for a few seconds, ranging callbacks start up immediately. This gives you a chance to do any quick ranging actions while your app is still running.

EDIT: Further investigation proves that notifyEntryStateOnDisplay has no effect on background monitoring, so the above should work regardless of whether you have this flag. See this detailed explanation and discussion of delays you may experience

KlimczakM
  • 11,178
  • 10
  • 55
  • 76
davidgyoung
  • 59,109
  • 12
  • 105
  • 181
  • Is the four minute range characteristic of sanctioned iBeacons or could it be a limitation of trying to emulate iBeacon? Four minutes is several blocks of walking away from the thing we want the user to see. – Joe Beuckman Oct 17 '13 at 21:04
  • My guess, and it is just a guess, is that iOS tries to save power by only doing a bluetooth scan every 4 minutes or so when no app is in the foreground ranging or monitoring. – davidgyoung Oct 18 '13 at 01:09
  • I'm not sure `notifiyEntryStateOnDisplay` has the effect you think it does. My understanding is that when that is set to `YES`, iOS defers notifying your app of region entry notifications until the user wakes the device's display, rather than sending them immediately. The example Apple gives in the WWDC presentations is a loyalty card app, where you wouldn't want to ping the phone as someone walks past the store, but you would want to present the notification when the user actively starts using their phone while inside of it. – Defragged Nov 20 '13 at 12:02
  • 1
    See my edited response above. I've done more testing that convinces me that this flag has no effect on background monitoring other than when you actually wake up the screen. And when the flag is set, background monitoring callbacks **are not deferred** You just get **additional** callbacks when the screen wakes up. See the detailed discussion and proof in the edit above. – davidgyoung Nov 20 '13 at 16:01
8

Code for iOS 9 to range beacons in the background, by using Location Updates:

  1. Open Project Settings -> Capabilities -> Background Modes -> Toggle Location Updates and Uses Bluetooth LE accessories to ON.

  2. Create a CLLocationManager, request Always monitoring authorization (don't forget to add the Application does not run in background to NO and NSLocationAlwaysUsageDescription in the app's info.plist) and set the following properties:

    locationManager!.delegate = self
    locationManager!.pausesLocationUpdatesAutomatically = false
    locationManager!.allowsBackgroundLocationUpdates = true
    
  3. Start ranging for beacons and monitoring region:

    locationManager!.startMonitoringForRegion(yourBeaconRegion)
    locationManager!.startRangingBeaconsInRegion(yourBeaconRegion)
    locationManager!.startUpdatingLocation()
    
    // Optionally for notifications
    UIApplication.sharedApplication().registerUserNotificationSettings(
        UIUserNotificationSettings(forTypes: .Alert, categories: nil))
    
  4. Implement the CLLocationManagerDelegate and in your didEnterRegion send both startRangingBeaconsInRegion() and startUpdatingLocation() messages (optionally send the notification as well) and set the stopRangingBeaconsInRegion() and stopUpdatingLocation() in didExitRegion

Be aware that this solution works but it is not recommended by Apple due to battery consumption and customer privacy!

More here: https://community.estimote.com/hc/en-us/articles/203914068-Is-it-possible-to-use-beacon-ranging-in-the-background-

Teodor Ciuraru
  • 2,949
  • 1
  • 27
  • 33
  • 1
    I'd recommend to take the "Be aware" part in bold, it's quite serious ;) Thanks for the answer and especially for the link! – mojuba Nov 03 '16 at 14:43
7

Here is the process you need to follow to range in background:

  1. For any CLBeaconRegion always keep monitoring on, in background or foreground and keep notifyEntryStateOnDisplay = YES
  2. notifyEntryStateOnDisplay calls locationManager:didDetermineState:forRegion: in background, so implement this delegate call...

...like this:

- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{

   if (state == CLRegionStateInside) {


        //Start Ranging
        [manager startRangingBeaconsInRegion:region];
    }

   else{

        //Stop Ranging
        [manager stopRangingBeaconsInRegion:region];
    }

}

I hope this helps.

KlimczakM
  • 11,178
  • 10
  • 55
  • 76
manishnath
  • 434
  • 1
  • 3
  • 12
  • can the location manager's delegate be any object or should the appdelegate do it? For me, it's only working when the appdelegate is the locationmanager's delegate...do you see any reason why? – rops Oct 29 '13 at 12:35
  • 2
    Yes it can work with any object - see this example https://github.com/manishnath/iBeaconCenter – manishnath Nov 01 '13 at 07:19
  • 1
    Minor comment: you'll need to typecast the region using (CLBeaconRegion *) as startRangingBeaconsInRegion expects a CLBeaconRegion * and didDetermineState passes a CLRegion * – CharlesA May 23 '14 at 10:19
2

You are doing two separate operations here - 'ranging' beacons and monitoring for a region. You can monitor for a region in the background, but not range beacons.

Therefore, your implementation of locationManager:didRangeBeacons:inRegion: won't get called in the background. Instead, your call to startMonitoringForRegion will result in one / some of the following methods being called:

– locationManager:didEnterRegion:
– locationManager:didExitRegion:
– locationManager:didDetermineState:forRegion:

These will get called in the background. You can at that point trigger a local notification, as in your original code.

James Frost
  • 6,780
  • 1
  • 29
  • 41
1

Your app should currently wake up if you're just wanting to be notified when you enter a beacon region. The only background restriction I know of concerns actually hosting an iBeacon on an iOS device. In that case, the app would need to be physically open in the foreground. For that situation, you'd be better off just doing the straight CoreBluetooth CBPeripheralManager implementation. That way you'd have some advertising abilities in the background.

Tommy Devoy
  • 13,279
  • 3
  • 46
  • 75
  • I did figure out that if I startMonitoringForRegion using that beacon as the region, then I will get notified on the lock screen if the app is running in the background. I would think that startMonitoringForRegion would launch the app if it isn't already running. – Aaron Bratcher Oct 02 '13 at 18:22
  • Per Apple's AirLocate sample code: "didDetermineState(): A user can transition in or out of a region while the application is not running. When this happens CoreLocation will launch the application momentarily, call this delegate method and we will let the user know via a local notification." – barbazoo Oct 09 '13 at 23:24
  • Yeah i didn't read the question right. I was only referring to iBeacon hosting in the background. ie if you're running an app thats hosting an iBeacon, the app needs to be running in the foreground for others to enter the region. I'll edit. – Tommy Devoy Oct 09 '13 at 23:55
  • 1
    Regardless, the CLBeaconLocation is not able to activate the app if it's not in the background, as in iOS 7.0.2. Very disappointing. – barbazoo Oct 10 '13 at 00:07
  • Yeah I had high hopes for the iBeacon stuff..but it turned out to only be a watered down version of CoreBluetooth and didn't improve on any of the pitfalls of CB – Tommy Devoy Oct 10 '13 at 00:23