When testing with a simple app to test beacon region monitoring I seem to get very inconsistent results depending on the device (not the device model, the specific device). The problem is that I don't receive the CLRegionStateInside state on the region after requestStateForRegion and didEnterRegion does not get called at all on these device. startRangingBeaconsinRegion: works fine but to conserve power and processing it is recommended to only start ranging when the didEnterRegion: method gets called. I tested this on 6 devices and it works on half on them (iPhone 5's) and does't work on one iPhone 5, one 5S and one 4S.
The beacons I use are the kontakt.io beacons.
This is the code to setup the region monitoring
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:BEACON_UUID];
CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
identifier:#"regionIdentifier"];
region.notifyOnEntry = YES;
region.notifyOnExit = YES;
region.notifyEntryStateOnDisplay = YES;
[self.locationManager startMonitoringForRegion:region];
[self.locationManager requestStateForRegion:region];
//If I enable this line, ranging starts on all devices
// [self.locationManager startRangingBeaconsInRegion:region];
I Found the problem. Apparently to use iBeacons in the way that is described in the documents users are required to have the Background Refresh setting enabled in the Settings. To check for this setting I use the following snippet:
if ([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusAvailable) {
NSLog(#"Background updates are available for the app.");
}else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusDenied)
{
NSLog(#"The user explicitly disabled background behavior for this app or for the whole system.");
}else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusRestricted)
{
NSLog(#"Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user.");
}
Found in this answer: Detecting user settings for Background App Refresh in iOS 7
Monitoring may not work for several reasons. One being that App Background Refresh is disabled to save your battery. Another one is neglecting to ensure you have setup the correct App Capabilities.
If this doesn't work there is a great post you can read that details all of the items to troubleshoot.
iBeacon StartMonitoringForRegion Doesn’t Work
Related
I'm currently working on app that requires iBeacon monitoring.
I wrote the app one year ago, using iOS 8.x SDK.
It was working as it was supposed to, but now, one year from then, the same code doesn't work anymore (I'm testing it with the same beacons!).
Beacon regions detection has become much more unpredictable.
It has a will of its own.
Some beacons get detected, some are just ignored.
I couldn't find anything relevant on OpenRadar.
A few people complained about something similar on Apple Dev Forums, but Apple never came back to them.
Thoughts?
This is how I initialize the location manager.
self.locationManager = [CLLocationManager new];
self.locationManager.delegate = self;
// Worst accuracy is set in order to preserve battery life.
self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
self.locationManager.allowsBackgroundLocationUpdates = YES;
// Required to keep the app living in the background.
// Background mode "Location Updates" is enabled.
[self.locationManager startUpdatingLocation];
I think before you were using geofences or CLCircular regions. Core location doesn't need any of that code to detect iBeacons. Try setting it up like this:
self.locationManager = [CLLocationManager new];
self.locationManager.delegate = self;
[self.locationManager startMonitoringForRegion:beaconRegion]; // Where "beaconRegion" is a CLBeaconRegion with a UUID that matches the beacon you want to detect (major & minor optional)
beaconRegion.notifyEntryStateOnDisplay = YES;
beaconRegion.notifyOnEntry = YES;
beaconRegion.notifyOnExit = YES;
You also need to add NSLocationAlwaysUsageDescription to your info.plist. Once all that is done, you should begin getting enter and exit events for your beacons through these two methods:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region;
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region;
I need to work with an Estimote beacon.
When the app is in foreground LocationMangaer delegate method didEnterInRegion end didExitRegion are called properly, but I have a problem when the application is in background (with locked screen).
If I leave the device on a table and if I walk away with Estimate beacon, after a few meters the method didExitRegion is called.
When I approach to the device and enter in the region of the beacon (a few centimeters) the method didEnterInRegion or method didDetermineState aren't called never. If I unlock the screen, after a few seconds, the method didEnterInRegion are called.
This is my code:
NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString:#"MYUUID"];
NSString *regionIdentifier = #"FuelPay";
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:beaconUUID identifier:regionIdentifier];
beaconRegion.notifyOnExit = YES;
beaconRegion.notifyOnEntry = YES;
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];
In the Info.plist I set NSLocationAlwaysUsageDescription property and I enable background mode (with "Uses Bluetooth LE accessories") in project capabilities.
What is wrong?
Thank you very much!
Background monitoring is NOT the same as foreground monitoring. It may take a while (in the order of minutes also) to detect a beacon in background since the scanning ratio is reduced (otherwise your battery will drain too fast).
Try your tests waiting a little bit more, if didExitRegion is called it's counterpart didEnterRegion should be called too.
Anyway I assume that before you check the didEnterRegion you have previously performed an exit. I mean that if you launch your app while you are already within the range of a beacon (e.g. beacon and device in the same room) there is no way didEnterRegion will ever be called. So you can
Trigger didExitRegion leaving the room
Launch your app without any beacon in range
and then get close to your beacon again
When there is a beacon in range of iOS device, I get notified(CLRegionStateInside) and can start ranging. This works properly. However, when ranging is started and the the iOS devices is not in range anymore, I don't get notified(State doesn't change to CLRegionStateOutside). Not in foreground or background.
Also didEnterRegion and didExitRegion never gets called. I start ranging in didDeterminState when state is CLRegionStateInside.
I do have background refresh settings enabled for my app.
When I start the app for the first time I do get an alert asking for location permission.
So basically, i'm able to start ranging, but i'm not able to stop ranging, because the state doesn't change to CLRegionStateOutside.
I use Xcode 6.3.1, iOS 8.3 on iPhone 4s.
This is my code:
INIT:
- (id)init{
self = [super init];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
if ([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization]; //or requestWhenInUseAuthorization
}
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:#"B75FA2E9-3D02-480A-B05E-0C79DBB922AD"];
self.myBeaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
identifier:#"TESTREGIO"];
[self.locationManager startMonitoringForRegion:self.myBeaconRegion];
[self.locationManager requestStateForRegion:self.myBeaconRegion];
self.myBeaconRegion.notifyEntryStateOnDisplay = YES;
self.myBeaconRegion.notifyOnEntry = YES;
self.myBeaconRegion.notifyOnExit = YES;
return self;
}
DETERMINESTATE:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
if (state == CLRegionStateInside)
{
[self.locationManager startRangingBeaconsInRegion:self.myBeaconRegion];
}else{
[self.locationManager stopRangingBeaconsInRegion:self.myBeaconRegion];
}
}
DIDENTERREGION and DIDEXITREGION:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion*)region
{
NSLog(#"DidenterRegion================================================");
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"DidexitRegion================================================");
}
You are not going to get a didExitRegion notification unless you were already inside a region. I suggest you track the state of the region your are looking for separately.
I don't understand what you are doing in didDetermineState. You started ranging for your beacon in init(). You would not need to start ranging again.
A few tips:
You do not need to request any background permissions for this to work.
calls to didExitRegion happen 3 seconds after a beacon is no longer detected when the device is ranging in the foreground. In the background, or in the foreground when not ranging, these calls can be delayed for up to 15 minutes. These delays always exist on iPhone 4S models. On newer models, the delays may or may not exist depending on whether there are enough hardware assist slots for detecting beacons. See details here: http://developer.radiusnetworks.com/2014/03/12/ios7-1-background-detection-times.html
I suspect the failure to get exit events is really an issue of them taking longer than expected. Wait 15 minutes and see if the callbacks come.
You should also call the following function to start ranging
[self.locationManager startRangingBeaconsInRegion:self.myBeaconRegion];
I've been working on iOS app which interacts with iBeacon devices. Workflow is next:
if user near iBeacon then app receiving push notification from internet.
So for recognizing if user near some iBeacon needs to be turned on next modules:
gps
bluetooth
wifi/3G
push notification
The issue is that without turned on GPS module app can't find any iBeacons. It's weird since iBeacon technology works using bluetooth only.
How to solve the following problem?
I use Xcode 6.1.1, iOS 8, CoreLocation and CoreBluetooth frameworks.
Here is a code how I implemented:
if ([CLLocationManager locationServicesEnabled]) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
if([_locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[_locationManager requestAlwaysAuthorization];
}
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:#"12345678-1234-1234-1234-123456789012"];
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
identifier:bundleIdentifier];
[_locationManager startMonitoringForRegion:beaconRegion];
[_locationManager startRangingBeaconsInRegion:beaconRegion];
}
else {
NSLog(#"location service is disabled");
}
You don't need GPS for iBeacon to work, but you do need Location Services.
This why I asked how you were "turning off the GPS", as I am not aware of any way in iOS that you can turn off the GPS receiver specifically.
When the user disables Location Services in the Settings app they aren't just turning off GPS - as the name says, they are turning off Location Services. Location Services in iOS refers to anything that can locate the user, which includes GPS, WiFi location and iBeacon.
No, without GPS, iBeacon will not work properly. CLLocation Manager is a class in the core location framework. The delegates of CLocation Manager will not get triggered without GPS. Here iBeacon works with the help of CLocation Manager.
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region;
This is the delegate that is getting triggered while an iBeacon is identified in a region. This delegate will not work without GPS.
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...
}
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:
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.)
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
Code for iOS 9 to range beacons in the background, by using Location Updates:
Open Project Settings -> Capabilities -> Background Modes -> Toggle Location Updates and Uses Bluetooth LE accessories to ON.
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
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))
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-
Here is the process you need to follow to range in background:
For any CLBeaconRegion always keep monitoring on, in background or foreground and keep notifyEntryStateOnDisplay = YES
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.
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.
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.