Lifecycle of CLLocationManager for a beacon region monitoring app - ios

I have noticed that whenever I start again the app and look at the following method I get that the beaconRegion returned is not nill. How does this work precisely?
beaconRegion = [self.locationManager.monitoredRegions member:beaconRegion];
// for more context on how I use this please look at the code below
In other words, how does iOS handle the allocation of CLLocationManager? Does it deserialise it every time the app is woken up and in this way retrieve the region information?
This is the output of the Xcode debugger console when running the code below:
2015-11-11 09:44:13.718 RegionMonitoringTest[239:15121] AppDelegate: creating new location manager object
2015-11-11 09:44:13.722 RegionMonitoringTest[239:15121] BeaconMonitoring class: in startRangingForBeacons, startupdatinglocation, range for my beacon
2015-11-11 09:44:13.724 RegionMonitoringTest[239:15121] Region already in list
2015-11-11 09:44:13.732 RegionMonitoringTest[239:15121] AppDelegate: Application did became active.
2015-11-11 09:44:13.762 RegionMonitoringTest[239:15121] BeaconMonitoring -> LocationManager: didFailWithError | Error: Error Domain=kCLErrorDomain Code=0 "(null)"
2015-11-11 09:44:13.762 RegionMonitoringTest[239:15121] Requesting to start ranging for beacons again.
2015-11-11 09:44:13.762 RegionMonitoringTest[239:15121] BeaconMonitoring class: in startRangingForBeacons, startupdatinglocation, range for my beacon
2015-11-11 09:44:13.767 RegionMonitoringTest[239:15121] Region already in list
Below I paste the source code that I am using to test this, it may be helpful (it is based on the AirLocate example provided by Apple):
#import "AppDelegate.h"
#define BEACON_REGION #"01020102-0102-0102-0102-010201020102"
#interface AppDelegate ()
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (strong, nonatomic) NSMutableArray * monitoredRegions;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
NSLog(#"AppDelegate: being woken up after entering region");
}
else{
NSLog(#"AppDelegate: creating new location manager object");
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.pausesLocationUpdatesAutomatically = false;
self.locationManager.allowsBackgroundLocationUpdates = true;
self.locationManager.delegate = self;
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
self.monitoredRegions = [[NSMutableArray alloc] initWithCapacity:10];
[self startRangingForBeacons];
}
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
NSLog(#"AppDelegate: will resign active");
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog(#"AppDelegate: did enter background");
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
//[self.bluetoothDataSync stopScanning];
NSLog(#"AppDelegate: did enter foreground");
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
NSLog(#"AppDelegate: Application did became active.");
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
//[[UIApplication sharedApplication] cancelAllLocalNotifications];
NSLog(#"AppDelegate: App will terminate");
}
//////////////////////////////////////////////////
- (void) startRangingForBeacons{
NSLog(#"BeaconMonitoring class: in startRangingForBeacons, startupdatinglocation, range for my beacon");
[self startMonitoringForRegion:[[NSUUID alloc] initWithUUIDString:BEACON_REGION] :#"my-beaconregion"];
}
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSString * message = [NSString stringWithFormat:#"BeaconMonitoring -> LocationManager: monitoringDidFailForRegion | Error: %#, Region identifier: %#", error, region.identifier];
NSLog(#"%#", message);
NSLog(#"Requesting to start ranging for beacons again.");
[self startRangingForBeacons];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSString * message = [NSString stringWithFormat:#"BeaconMonitoring -> LocationManager: didFailWithError | Error: %#", error];
NSLog(#"%#", message);
NSLog(#"Requesting to start ranging for beacons again.");
[self startRangingForBeacons];
}
- (void) startMonitoringForRegion:(NSUUID*)beaconUUID :(NSString*)regionIdentifier{
CLBeaconRegion *beaconRegion = nil;
beaconRegion = [self.locationManager.monitoredRegions member:beaconRegion];
if(beaconRegion)
{
NSLog(#"Region already in list");
}
else{
beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:beaconUUID identifier:regionIdentifier];
beaconRegion.notifyEntryStateOnDisplay = YES;
beaconRegion.notifyOnEntry = YES;
beaconRegion.notifyOnExit = YES;
[self.locationManager startMonitoringForRegion:beaconRegion];
}
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
[self.locationManager startUpdatingLocation];
[self.monitoredRegions addObject:beaconRegion];
}
- (void) stopRangingForbeacons{
NSLog(#"BeaconMonitoring: stopRangingForbeacons - Stops updating location");
[self.locationManager stopUpdatingLocation];
for (int i=0; i < [self.monitoredRegions count]; i++) {
NSObject * object = [self.monitoredRegions objectAtIndex:i];
if ([object isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion * region = (CLBeaconRegion*)object;
[self.locationManager stopMonitoringForRegion:region];
[self.locationManager stopRangingBeaconsInRegion:region];
}
else{
NSLog(#"BeaconMonitoring: unrecongized object in beacon region list");
}
}
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
if (![CLLocationManager locationServicesEnabled]) {
NSLog(#"Couldn't turn on ranging: Location services are not enabled.");
}
if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorized) {
NSLog(#"Couldn't turn on monitoring: Location services not authorised.");
}
}
#pragma CLLocationManagerDelegate
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"BeaconMonitoring: did enter region, will now start ranging beacons in this region");
[manager startRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"BeaconMonitoring: did exit region, will now: stop ranging beacons in this region; stop updating locations; stop scanning for BLE");
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager stopUpdatingLocation];
NSDictionary * notificationData = #{ #"value" : #"exitedRegion"};
[[NSNotificationCenter defaultCenter] postNotificationName:#"dataUpdate" object:nil userInfo:notificationData];
}
- (void) locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSString * message = #"CLRegionState: ";
switch (state) {
case CLRegionStateInside:
message = #"CLRegionState: state inside";
break;
case CLRegionStateOutside:
message = #"CLRegionState: state outside";
break;
case CLRegionStateUnknown:
message = #"CLRegionState: state unknown";
break;
default:
message = #"CLRegionState: default case";
break;
}
NSLog(#"%#", message);
}
-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
bool rangedBeacons = false;
if ([beacons count]>0) {
for (int i=0; i<[beacons count]; i++) {
CLBeacon *beacon = [beacons objectAtIndex:i];
if((beacon.major.intValue == 4) && (beacon.major.intValue == 8)){
rangedBeacons = true;
}
}
}
if (rangedBeacons) {
NSLog(#"Region identifier: %#", region.identifier);
NSString * message = [NSString stringWithFormat:#"BeaconMonitoring: ranged a total of %lu, hence request scan start", (unsigned long)[beacons count]];
NSLog(#"%#", message);
}
}

Since Location Manager's monitoring works even when the app is not running, e.g., was terminated due to memory pressure or killed by the user at the app switcher, iOS needs to keep the list of regions apps monitor for somewhere outside the app, and you can think of monitoredRegions as the reflection of that list—rather than an instance property that vanishes when the object gets deallocated.
Note for example that if you instantiate more than one location manager, they all share the same monitoring list—a direct outcome of the list being stored per-app somewhere on the OS level. If you start/stop monitoring for a region in one location manager, it'll affect all the others.
All of this applies to CLBeaconRegion, but also to "regular" CLCircularRegion—because it's a feature of monitoring, and not something beacon-specific.

Related

iOS iBeacon / Bluetooth connectivity when app is dead and gone

What I need:
A predictable, solid and reliable way of launching iBeacon delegate methods such as didDetermineState, didRangeBeacons, didEnterRegion or didExitRegion when the app is dead and the device is plugged in and nearby.
The Current Situation
I am making an app for parents to use for their kids to help them shut down their phones during important times. The app is in Objective-C and it needs to maintain a persistent connection to a bluetooth device even after the life of the application.
I have been trying for a long time to get this to work and I have had help from a lot of S.O. posters and currently I know that I must use iBeacon in my device to launch from terminated (that's the only reason I use it, I would gladly dump it if there was another way to launch an app from terminated). To clarify, I need 2 things here in the same device (which I have already built) iBeacon and a solid BT connection. I need this device connection pairing because this is the only way to send/receive commands from the BT device. What I have discovered is that the didRange or didEnter delegate methods that fire in the background are unreliable at best. They don't always fire right away and they only fire a few times and the whole thing dies (which I now know this 10 second window is expected behaviour from a terminated app). I have even had full entire days where I plug/unplug it constantly looking for any sign that the app has come back to life and nothing happens...
When the app is open, things work fine, however when the app is nearby to my beacon/bluetooth I want it to launch a sort of makeshift lock screen inside the app. I am already doing this part fairly well when the app is in the foreground. If a kid tries to close the app or background it I want to respond by having my BT device launch into the background once it's terminated (I know the UI won't come up and that's fine I just need a series of functions to fire). It will then connect to bluetooth and receive some commands from the device. Sounds simple enough eh? Here's were things get messy.
Some context: I have all background modes added in info.plist for bluetooth and beacon and everything works fine when the app is in foreground...
If the iBeacon is detected in range, I then want to use that 10 second window to connect via BT pairing to my box and send through a command. So far, this is wonky... The iBeacon ranging functions do not fire when the app is terminated they only fire on the strangest of use cases. I cannot seem to predict when they are going to fire.
My Code
ibeaconManager.h
#interface IbeaconManager : NSObject
#property (nonatomic) BOOL waitingForDeviceCommand;
#property (nonatomic, strong) NSTimer *deviceCommandTimer;
+ (IbeaconManager *) sharedInstance;
- (void)startMonitoring;
- (void)stopMonitoring;
- (void)timedLock:(NSTimer *)timer;
#end
ibeaconManager.m
#interface IbeaconManager () <CLLocationManagerDelegate>
#property (nonatomic, strong) BluetoothMgr *btManager;
#property (nonatomic, strong) CLLocationManager *locationManager;
#property (nonatomic, strong) CLBeaconRegion *region;
#property (nonatomic) BOOL connectedToDevice;
#end
NSString *const PROXMITY_UUID = #"00000000-1111-2222-3333-AAAAAAAAAAAA";
NSString *const BEACON_REGION = #"MY_CUSTOM_REGION";
const int REGION_MINOR = 0;
const int REGION_MAJOR = 0;
#implementation IbeaconManager
+ (IbeaconManager *) sharedInstance {
static IbeaconManager *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[IbeaconManager alloc] init];
});
return _sharedInstance;
}
- (id)init {
self = [super init];
if(self) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager requestAlwaysAuthorization];
self.connectedToDevice = NO;
self.waitingForDeviceCommand = NO;
self.region = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:PROXMITY_UUID]
major:REGION_MAJOR
minor:REGION_MINOR
identifier:BEACON_REGION];
self.region.notifyEntryStateOnDisplay = YES;
self.region.notifyOnEntry = YES;
self.region.notifyOnExit = YES;
}
return self;
}
- (void)startMonitoring {
if(self.region != nil) {
NSLog(#"**** started monitoring with beacon region **** : %#", self.region);
[self.locationManager startMonitoringForRegion:self.region];
[self.locationManager startRangingBeaconsInRegion:self.region];
}
}
- (void)stopMonitoring {
NSLog(#"*** stopMonitoring");
if(self.region != nil) {
[self.locationManager stopMonitoringForRegion:self.region];
[self.locationManager stopRangingBeaconsInRegion:self.region];
}
}
- (void)triggerCustomLocalNotification:(NSString *)alertBody {
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = alertBody;
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
}
#pragma mark - CLLocationManager delegate methods
- (void)locationManager:(CLLocationManager *)manager
didDetermineState:(CLRegionState)state
forRegion:(CLRegion *)region {
NSLog(#"did determine state STATE: %ld", (long)state);
NSLog(#"did determine state region: %#", region);
[self triggerCustomLocalNotification:#"made it into the did determine state method"];
NSUInteger appState = [[UIApplication sharedApplication] applicationState];
NSLog(#"application's current state: %ld", (long)appState);
if(appState == UIApplicationStateBackground || appState == UIApplicationStateInactive) {
NSString *notificationText = #"Did range beacons... The app is";
NSString *notificationStateText = (appState == UIApplicationStateInactive) ? #"inactive" : #"backgrounded";
NSString *notificationString = [NSString stringWithFormat:#"%# %#", notificationText, notificationStateText];
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
bool isAppLockScreenShowing = [userDefaults boolForKey:#"isAppLockScreenShowing"];
if(!isAppLockScreenShowing && !self.waitingForDeviceCommand) {
self.waitingForDeviceCommand = YES;
self.deviceCommandTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(timedLock:)
userInfo:notificationString
repeats:NO];
}
} else if(appState == UIApplicationStateActive) {
if(region != nil) {
if(state == CLRegionStateInside) {
NSLog(#"locationManager didDetermineState INSIDE for %#", region.identifier);
[self triggerCustomLocalNotification:#"locationManager didDetermineState INSIDE"];
} else if(state == CLRegionStateOutside) {
NSLog(#"locationManager didDetermineState OUTSIDE for %#", region.identifier);
[self triggerCustomLocalNotification:#"locationManager didDetermineState OUTSIDE"];
} else {
NSLog(#"locationManager didDetermineState OTHER for %#", region.identifier);
}
}
//Upon re-entry, remove timer
if(self.deviceCommandTimer != nil) {
[self.deviceCommandTimer invalidate];
self.deviceCommandTimer = nil;
}
}
}
- (void)locationManager:(CLLocationManager *)manager
didRangeBeacons:(NSArray *)beacons
inRegion:(CLBeaconRegion *)region {
NSLog(#"Did range some beacons");
NSUInteger state = [[UIApplication sharedApplication] applicationState];
NSString *notificationStateText = (state == UIApplicationStateInactive) ? #"inactive" : #"backgrounded";
NSLog(#"application's current state: %ld", (long)state);
[self triggerCustomLocalNotification:[NSString stringWithFormat:#"ranged beacons, application's current state: %#", notificationStateText]];
if(state == UIApplicationStateBackground || state == UIApplicationStateInactive) {
NSString *notificationText = #"Did range beacons... The app is";
NSString *notificationString = [NSString stringWithFormat:#"%# %#", notificationText, notificationStateText];
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
bool isAppLockScreenShowing = [userDefaults boolForKey:#"isAppLockScreenShowing"];
if(!isAppLockScreenShowing && !self.waitingForDeviceCommand) {
self.waitingForDeviceCommand = YES;
self.deviceCommandTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(timedLock:)
userInfo:notificationString
repeats:NO];
}
} else if(state == UIApplicationStateActive) {
if(self.deviceCommandTimer != nil) {
[self.deviceCommandTimer invalidate];
self.deviceCommandTimer = nil;
}
}
}
- (void)timedLock:(NSTimer *)timer {
self.btManager = [BluetoothMgr sharedInstance];
[self.btManager sendCodeToBTDevice:#"magiccommand"
characteristic:self.btManager.lockCharacteristic];
[self triggerCustomLocalNotification:[timer userInfo]];
self.waitingForDeviceCommand = NO;
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"Did Enter Region: %#", region);
[self triggerCustomLocalNotification:[NSString stringWithFormat:#"Did enter region: %#", region.identifier]];
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"Did Exit Region: %#", region);
[self triggerCustomLocalNotification:[NSString stringWithFormat:#"Did exit region: %#", region.identifier]];
//Upon exit, remove timer
if(self.deviceCommandTimer != nil) {
[self.deviceCommandTimer invalidate];
self.deviceCommandTimer = nil;
}
}
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSLog(#"monitoringDidFailForRegion EPIC FAIL for region %# withError %#", region.identifier, error.localizedDescription);
}
#end
I have built a similar system for iOS that uses iBeacon transmissions to wake up in the background and then connect to bluetooth LE to exchange data. Rest assured that this is all possible, it's just tricky to get working and trickier to debug.
A few tips on doing this with Bluetooth LE connecting:
Beacon ranging functions will not fire when the app is killed unless you also monitor for the beacons and get a didEnter or didExit transition, which will re-launch the app into the background for 10 secs as you describe. Again, this will only happen if you transition from in region to out of region or vice versa. This is tricky to test, because you may not realize CoreLocation thinks you are "in region" when you kill the app, but you won't get a wakeup event for detecting the beacon.
In order to get bluetooth events in the background, you need to make sure your Info.plist declares this:
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
</array>
If that is not present, you absolutely will not get callbacks to didDiscoverPeripheral in the background.
You will need to start scanning for bluetooth when your app starts up, and connect when you get a callback to func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
Save off a copy of the peripheral instance from above because you only get one callback in the background for discovery from each unique bluetooth device. If your connection fails, you can retry with the same peripheral object instance.
In order to debug re-launching from a killed state, I add lots of NSLog statements (I add the ability to turn them on and off in code) and then look for these in XCode's Windows -> Devices -> My iPhone panel, where you can expand the little arrow at the bottom of the screen to show the logs for all apps on the device. You absolutely will see logs here for your app if it is relaunched from a killed state.

Understanding iBeacons in iOS: didDetermineState and didEnterRegion events

Part one:
I have written the following code to monitor iBeacons. I would like to detect the didEnterRegion and didExitRegion event. However it never happens. Would you be able to take a look at the code and suggest what could be missing?
I use the Apple AirLocate sample code to configure one device as iBeacon and perform the following steps to test my code:
Steps:
compile and execute AirLocate sample code on device B
compile and execute this code on device A
in device B use AirLocate app to configure device as iBeacon choosing the following UUID: "74278BDA-B644-4520-8F0C-720EAF059935"
Results:
state inside message
Expected results:
state inside message
did enter region
Why is that?
Those are my plist entries:
Code:
#import "BeaconMonitoring.h"
#implementation BeaconMonitoring
- (instancetype)init
{
self = [super init];
if (self) {
self.locationManager = [[CLLocationManager alloc] init];
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
self.locationManager.delegate = self;
self.locationManager.pausesLocationUpdatesAutomatically = NO;
self.monitoredRegions = [[NSMutableArray alloc] initWithCapacity:10];
}
return self;
}
- (void) startRangingForBeacons{
NSLog(#"in startRangingForBeacons");
[self.locationManager startUpdatingLocation];
[self startMonitoringForRegion:[[NSUUID alloc] initWithUUIDString:#"74278BDA-B644-4520-8F0C-720EAF059935"] :#"b"];
}
- (void) startMonitoringForRegion:(NSUUID*)beaconUUID :(NSString*)regionIdentifier{
/**
Alternatively:
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:#"xxxx"
major:10
minor:20
identifier:#"name"]
**/
// Override point for customization after application launch.
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:beaconUUID identifier:regionIdentifier];
beaconRegion.notifyEntryStateOnDisplay = NO;
beaconRegion.notifyOnEntry = YES;
beaconRegion.notifyOnExit = YES;
[self.locationManager startMonitoringForRegion:beaconRegion];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
[self.monitoredRegions addObject:beaconRegion];
}
- (void) stopRangingForbeacons{
NSLog(#"in stopRangingForbeacons");
[self.locationManager stopUpdatingLocation];
for (int i=0; i < [self.monitoredRegions count]; i++) {
NSObject * object = [self.monitoredRegions objectAtIndex:i];
if ([object isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion * region = (CLBeaconRegion*)object;
[self.locationManager stopMonitoringForRegion:region];
[self.locationManager stopRangingBeaconsInRegion:region];
}
else{
NSLog(#"Serious error, should never happen!");
}
}
}
#pragma CLLocationManagerDelegate
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
[manager startRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager startUpdatingLocation];
NSLog(#"You entered the region.");
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager stopUpdatingLocation];
NSDictionary * notificationData = #{ #"value" : #"exitedRegion"};
[[NSNotificationCenter defaultCenter] postNotificationName:#"dataUpdate" object:nil userInfo:notificationData];
NSLog(#"You exited the region.");
// [self sendLocalNotificationWithMessage:#"You exited the region."];
}
- (void) locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSLog(#"did determine state");
switch (state) {
case CLRegionStateInside:
NSLog(#"state inside");
break;
case CLRegionStateOutside:
NSLog(#"state outside");
break;
case CLRegionStateUnknown:
NSLog(#"state unknown");
break;
default:
NSLog(#"Default case: Region unknown");
break;
}
}
-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
NSLog(#"Did range %lu beacon in region %#", (unsigned long)[beacons count], region.identifier);
NSString * visibleInformation = [NSString stringWithFormat:#"(%lu)", (unsigned long)[beacons count]];
for (int i=0; i<[beacons count]; i++) {
CLBeacon *beacon = [beacons objectAtIndex:i];
if ([beacons count] == 1) {
NSNumber * distance = [NSNumber numberWithFloat:beacon.accuracy];
visibleInformation = [NSString stringWithFormat:#"%i-%i is %f", beacon.major.intValue, beacon.minor.intValue, distance.doubleValue];
}
else{
visibleInformation = [visibleInformation stringByAppendingString:[NSString stringWithFormat:#" %i-%i ", beacon.major.intValue, beacon.minor.intValue]];
}
}
}
#end
Part two:
I had a look at the AirLocate source code to understand if there was something that I had to trigger in the state inside message to get the monitoring working properly. However I found that the ** didDetermineState** method is implemented in the AppDelegate.
Why is that?
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
/*
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.
*/
UILocalNotification *notification = [[UILocalNotification alloc] init];
if(state == CLRegionStateInside)
{
notification.alertBody = NSLocalizedString(#"You're inside the region", #"");
}
else if(state == CLRegionStateOutside)
{
notification.alertBody = NSLocalizedString(#"You're outside the region", #"");
}
else
{
return;
}
/*
If the application is in the foreground, it will get a callback to application:didReceiveLocalNotification:.
If it's not, iOS will display the notification to the user.
*/
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
Assumption: R = Region made by B and listened to by A
Case 1
IF A was started before B:
app A should tell you determine state for region R = outside
app A should run didEnter Region R
case 2
IF A was infact started after B:
it should only run determineState Region R = inside
the end. There is no 2 here because it never enters the range. it was started inside of it

iBeacon monitoring/ranging after device restarts

I built a simple ios app with IBeacon, when the app is in foreground or background it works ok but after rebooting my phone, my app stops getting CoreLocation delegate callbacks. This is my AppDelegate.m code.
#import "AppDelegate.h"
#import <CoreLocation/CoreLocation.h>
#import "ViewController.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]) {
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound
categories:nil]];
}
if(launchOptions != nil){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"alerta"
message:[launchOptions[UIApplicationLaunchOptionsLocalNotificationKey] description]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString:#"B39ED98FF-2900-441A-802F-9C398FC199D2"];
NSString *regionIdentifier = #"iBeacons region 1";
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID: beaconUUID identifier: regionIdentifier ];
beaconRegion.notifyEntryStateOnDisplay = YES;
self.locationManager = [[CLLocationManager alloc]init];
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]){
[self.locationManager requestAlwaysAuthorization];
}
self.locationManager.delegate = self;
//self.locationManager.pausesLocationUpdatesAutomatically = NO;
[self.locationManager startMonitoringForRegion:beaconRegion];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
[self.locationManager startUpdatingLocation];
return YES;
}
-(void)sendLocalNotificationWithMessage:(NSString*)message {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = message;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
- (void) locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region{
ViewController *viewController = (ViewController*) self.window.rootViewController;
viewController.beacons = beacons;
[viewController.tableView reloadData];
NSString *message = #"";
if(beacons.count > 0){
CLBeacon *nearestBeacon = beacons.firstObject;
if(nearestBeacon.proximity == self.lastProximity || nearestBeacon.proximity == CLProximityUnknown){
return;
}
self.lastProximity = nearestBeacon.proximity;
switch (nearestBeacon.proximity) {
case CLProximityFar:
message = #"CLProximityFar";
break;
case CLProximityNear:
message= #"CLProximityNear";
break;
case CLProximityImmediate:
message= #"CLProximityImmediate";
break;
case CLProximityUnknown:
return;
}
}else {
message = #"No BEACONS";
}
NSLog(#"%#", message);
[self sendLocalNotificationWithMessage:message];
}
- (void) locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
[manager startRangingBeaconsInRegion:(CLBeaconRegion*) region];
[self.locationManager startUpdatingLocation];
NSLog(#"INSIDE REGION");
[self sendLocalNotificationWithMessage:#"INSIDE REGION"];
}
- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*) region];
[self.locationManager stopUpdatingLocation];
NSLog(#"OUTSIDE REGION");
[self sendLocalNotificationWithMessage:#"OUTSIDE REGION"];
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
if (state == CLRegionStateInside) {
//Start Ranging
[manager startRangingBeaconsInRegion:(CLBeaconRegion*) region];
[self.locationManager startUpdatingLocation];
NSLog(#"INSIDE REGION");
[self sendLocalNotificationWithMessage:#"INSIDE REGION"];
}
else{
//Stop Ranging
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*) region];
[self.locationManager stopUpdatingLocation];
NSLog(#"OUTSIDE REGION");
[self sendLocalNotificationWithMessage:#"OUTSIDE REGION"];
}
}
-(void)application:(UIApplication *)application didReceiveLocalNotification:(NSDictionary *)userInfo {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"ALERT"
message:#"didReceiveLocalNotification"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
I cant find what is the issue, i need your help!
The code looks OK. When testing this, I would recommend you do the following:
Launch your app
Turn on your beacon
Verify you get a didEnterRegion callback.
Turn off your beacon
Verify you get a didExitRegion callback.
Reboot your phone
Wait at least 2 minutes (it takes a bit of time before CoreLocation fully starts up after reboot.)
Turn on your beacon
Wait a few minutes to see if you get a didEnterRegion callback.

startMonitoringForRegion is not calling didEnterRegion if the app is started within the region

I'm having an issue where my app will not fire the didEnterRegion event if I start the app within the region. If I start the app outside the region and then enter the region, it fires. If I start the app inside the region, then leave the region, then re-enter the region, it fires.
Any suggestions on how to get it to fire as soon as the app is opened if it's in the region would be much appreciated!
I suggest you to use this code
[locationManager requestStateForRegion:region];
And use the delegate method didDetermineState: to check if the state is CLRegionStateInside or CLRegionStateOutside.
I don't think you can do that.
But, you can get the current location and check if it's inside the region you're specifying yourself. CLCircularRegion has a containsCoordinate: method for this.
The first conclusion is that didEnterRegion is implemented consistently with its name. :)
Implement something like this in your CLLocationManagerDelegate:
- (void) locationManager: (CLLocationManager *) manager
didStartMonitoringForRegion: (CLRegion *) region
{
if ([self insideRegion: region location: manager.location])
[self locationManager: manager
didEnterRegion: region];
}
From apple's documentation:
Monitoring of a geographical region begins immediately after
registration for authorized apps. However, don’t expect to receive an
event right away, because only boundary crossings generate an event.
In particular, if the user’s location is already inside the region
at registration time, the location manager doesn’t automatically
generate an event. Instead, your app must wait for the user to cross
the region boundary before an event is generated and sent to the
delegate. To check whether the user is already inside the boundary
of a region, use the requestStateForRegion: method of the
CLLocationManager class.
So I ended up doing this:
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong) NSDictionary *regionDictionary;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// setup regions in case you have multiple regions
self.regionDictionary = #{#"com.test" : #"2FAE2A83-1634-443B-8A0C-56704F81A181"};
// setup location manager
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
[self.locationManager startUpdatingLocation];
//start monitoring for all regions
for (NSString *key in self.regionDictionary.allKeys) {
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:self.regionDictionary[key]] identifier:key];
[self.locationManager startMonitoringForRegion:beaconRegion];
}
}
- (void)locationManager:(CLLocationManager*)manager didEnterRegion:(CLRegion *)region {
if (region.identifier.length != 0) {
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:self.regionDictionary[region.identifier]] identifier:region.identifier];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
}
}
- (void)locationManager:(CLLocationManager*)manager didExitRegion:(CLRegion *)region {
if (region.identifier.length != 0) {
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:self.regionDictionary[region.identifier]] identifier:region.identifier];
[self.locationManager stopRangingBeaconsInRegion:beaconRegion];
}
}
- (void)locationManager:(CLLocationManager*)manager didRangeBeacons:(NSArray*)beacons inRegion:(CLBeaconRegion*)region {
// Beacon found!
CLBeacon *foundBeacon = [beacons firstObject];
NSLog(#"UUID:%#; major:%#; minor:%#;", foundBeacon.proximityUUID.UUIDString, foundBeacon.major, foundBeacon.minor);
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
if ([region isKindOfClass:[CLBeaconRegion class]] && state == CLRegionStateInside) {
[self locationManager:manager didEnterRegion:region];
}
}
- (void)locationManager:(CLLocationManager *) manager didStartMonitoringForRegion:(CLRegion *) region {
[manager requestStateForRegion:region];
}

recieving location updates after app is terminated

I need to keep track of the user location all the time (but not drain the battery).
I understand the only way to get updates after app is terminated is using startMonitoringSignificantLocationChanges.
From Apple's Location Awareness Programming Guide on startMonitoringSignificantLocationChanges:
If you start this service and your application is subsequently
terminated, the system automatically relaunches the application into
the background if a new event arrives. In such a case, the options
dictionary passed to the application:didFinishLaunchingWithOptions:
method of your application delegate contains the key
UIApplicationLaunchOptionsLocationKey to indicate that your
application was launched because of a location event. Upon relaunch,
you must still configure a location manager object and call this
method to continue receiving location events. When you restart
location services, the current event is delivered to your delegate
immediately. In addition, the location property of your location
manager object is populated with the most recent location object even
before you start location services.
I would be glad if someone could demonstrate in the code (give an example) which methods i should use
In the following code i'm tring to :
- start the location manager at appdelegate which strats the signinficant monitor changes update and startupdating.
- in didUpdateToLocation i'm calling stopupdating
- in didFinishLaunchingWithOptions when i check if i got a UIApplicationLaunchOptionsLocationKey in order to know if i'm in the background and launched due to siginificant monitor location update.
- if so, i call startMonitoringSignificantLocationChanges again (not sure why...)
and begin a UIBackgeoundTaskIdentifier for calling startupdating method.
LocationController.m :
+ (LocationController*)sharedInstance {
#synchronized(self) {
if (sharedCLDelegate == nil) {
[[self alloc] init];
}
}
return sharedCLDelegate;
}
- (id)init
{
self = [super init];
if (self != nil) {
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
[self.locationManager startUpdatingLocation];
[self.locationManager startMonitoringSignificantLocationChanges];
}
return self;
}
- (void) startMonitoringSignificantLocationChanges
{
[self.locationManager startMonitoringSignificantLocationChanges];
}
- (void) stopMonitoringSignificantLocationChanges
{
[self.locationManager stopMonitoringSignificantLocationChanges];
}
-(void) start{
[self.locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation{
if ( abs([newLocation.timestamp timeIntervalSinceDate: [NSDate date]]) < 30) {
self.lastLocation = newLocation;
[self updateLocation]; //sending location to server
[self.locationManager stopUpdatingLocation];
}
}
- (void)locationManager:(CLLocationManager*)manager
didFailWithError:(NSError*)error{
[self.locationManager stopUpdatingLocation];
}
AppDelegate.h :
#interface AppDelegate : NSObject <UIApplicationDelegate> {
UIBackgroundTaskIdentifier bgTask;
}
AppDelegate.m :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
id locationValue = [launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey];
if (locationValue) {
[[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[[LocationController sharedInstance] start]; //startupdating
return YES;
}
else {
[[LocationController sharedInstance] init];
}
}
-(void) applicationDidEnterBackground:(UIApplication *) application
{
NSLog(#"entered background Mode");
}
-(void) applicationDidBecomeActive:(UIApplication *) application
{
NSLog(#"application Did Become Active");
}
Thank you.
Using your classes, this is what I would do.
In your AppDelegate.m, when your app is in the foreground or background, I'd move the CLLocationManager to run in the foreground / background to match. The reason I'm doing this is because if the CLLocationManager is not moved to the background when the app is in the background, no location updates are sent to the CLLocationManager's callback
- (void)applicationDidEnterBackground:(UIApplication *) application {
[[LocationController sharedInstance] stop];
[[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
NSLog(#"entered background Mode");
}
- (void)applicationDidBecomeActive:(UIApplication *) application {
[[LocationController sharedInstance] stopMonitoringSignificantLocationChanges];
[[LocationController sharedInstance] start];
NSLog(#"application Did Become Active");
}
So lets say your app then moves to the background, and after awhile, iOS decides it's using too much memory and kills your app.
A few minutes later, iOS then receives a location update, and respawns your app, letting it know it respawned due to a location service. You then need to re-start the background location service, as it will be the only chance your app has to do so.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
[[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
}
return YES;
}
Oh, and one last change, I'm not sure why in your locationManager:didUpdateToLocation:fromLocation: method you're stopping the location service, as when you do that no more updates come through. Just leave it running, then every time a location change comes through you can send that to the server.
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
if ( abs([newLocation.timestamp timeIntervalSinceDate: [NSDate date]]) < 30) {
self.lastLocation = newLocation;
[self updateLocation]; //sending location to server
}

Resources