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];
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 write an app that uses beacons and everything is working fine when the app is in the foreground. However when I press the power button and dim the screen the app is not finding any beacons anymore. The app is still hitting the:
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)clBeacons inRegion:(CLBeaconRegion *)region
method, but the logger tells me what 0 beacons has been found, which is weird, because due to the documentation, this methods should be called only when there are any beacons in range:
Tells the delegate that one or more beacons are in range.
I already have kCLAuthorizationStatusAuthorizedAlways permission from user (it's added to .plist file as well) and I have added these capabilities to the project:
This is my code for ranging beacons:
- (instancetype)init {
self = [super init];
if (self) {
NSUUID *proximityUUID = [[NSUUID alloc] initWithUUIDString:[Registry environment].config.beaconUuid];
self.region = [[CLBeaconRegion alloc] initWithProximityUUID:proximityUUID identifier:proximityUUID.UUIDString];
self.locationManager = [CLLocationManager new];
self.locationManager.delegate = self;
}
return self;
}
- (void)stopMonitoringBeacons {
[self.locationManager stopMonitoringForRegion:self.region];
DDLogInfo(#"Stopped monitoring beacons.");
}
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[manager requestStateForRegion:region];
[self startRangingBeacons];
}
- (void)startRangingBeacons {
[self.locationManager startRangingBeaconsInRegion:self.region];
DDLogInfo(#"Started ranging beacons.");
}
- (void)locationManager:(CLLocationManager *)manager rangingBeaconsDidFailForRegion:(CLBeaconRegion *)region withError:(NSError *)error {
DDLogInfo(#"Beacon ranging failed with error: %#.", error.localizedDescription);
}
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)clBeacons inRegion:(CLBeaconRegion *)region {
DDLogDebug(#"Found %tu beacons.", clBeacons.count);
// call the server (no worries, not all the time)
}
Any idea what am I missing? Or maybe I can't trust these logs?
SOLVED:
The problem was the way I've tested it. As #davidgyoung mentioned, ranging works only in foreground and if we want to run it in background we need to start it from monitoring, which informs us about new beacons in background. I already did it in my code:
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[manager requestStateForRegion:region];
[self startRangingBeacons];
}
But this method is called only if we meet new beacon. I was already simulating beacon when my app was in foreground, so when I dimmed the screen with power button, ranging was stopped and monitoring was never called - I would have to turn the beacon off/on or walk out/in of the beacon range to call this method again.
Beacon ranging on iOS generally only works in the foreground, and for about 10 seconds after your app first moves to the background. Beacon monitoring on the other hand can work in the background and wake your app up when a beacon is detected. If you do both ranging and monitoring simultaneously, you will again get 10 seconds of background ranging after a monitoring trigger wakes your app up in the background.
You can extend background ranging from 10 seconds to 3 minutes on request. I wrote a blog post on how to do this.
I'm using local notifications triggered by iBeacons in my app. It works fine both in foreground and background as long as the iPhone is active, but doesn't trigger didEnterRegion after about 15 minutes of inactivity or reboot.
It then only fires again on wake up of the iPhone with the home button or sleep button, but I want didEnterRegion to "fire" when the iPhone is in a pocket for 15 minutes or more while entering the region as well.
Is this possible? If yes, how?
Background modes > Location updates is disabled
Some code:
.h
#property (strong, nonatomic) CLBeaconRegion *beaconRegion;
#property (strong, nonatomic) CLLocationManager *locationManager;
.m
- (void)start {
self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:#"com.bla.bla"];
self.beaconRegion.notifyOnEntry = YES;
self.beaconRegion.notifyOnExit = YES;
self.beaconRegion.notifyEntryStateOnDisplay = YES;
self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
[self.locationManager startUpdatingLocation];
[self.locationManager startMonitoringForRegion:self.beaconRegion];
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
[self.locationManager requestStateForRegion:self.beaconRegion];
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
[self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
}
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
{
NSLog(#"%#", [error localizedDescription]);
}
-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
if (state == CLRegionStateInside) {
[manager startRangingBeaconsInRegion:self.beaconRegion];
} else {
[manager stopRangingBeaconsInRegion:self.beaconRegion];
}
}
I'm not sure what is going on here, but the experience described in the question is not consistent with what I have seen testing on multiple devices. Posting the code that sets this up might help figure out some answers.
In several apps, I have been able to get background didEnterRegion callbacks, even after much more than 15 minutes of inactivity without pressing the shoulder button or the home button. And to do this, I have not had to set any background modes. (Apple will actually reject your app if you submit it to the store with background mode Location updates set unnecessarily.)
There is a bug in iOS 7.1 that cam halt iBeacon detections at some point after boot, so perhaps this is what is going on in this case. Details are here. Unfortunately, testing this hypothesis would require you to wake up the screen in order to turn bluetooth off and on to clear the condition, and that would wake up your screen and give you the region exit anyway. Perhaps you could try setting beaconregion.notifyEntryStateOnDisplay=NO, recreating this condition then try cycling bluetooth to see if you then get the notification. You can also use an off the shelf beacon scanning app like Locate for iBeacon to see if your device is able to range for iBeacons at all after it gets into this state, and only cycle to Bluetooth if you are unable to detect iBeacons.
I'm trying to make applications, which ranges CLBeaconRegion.
I watched video from WWDC and presenter said, that I should call startMonitoringForRegion and, then user is inside region, startRangingBeaconsInRegion. I tried.
if (!_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
CLBeaconRegion *targetRegion = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:UUIDString] identifier:identifier];
targetRegion.notifyEntryStateOnDisplay = YES;
targetRegion.notifyOnEntry = YES;
targetRegion.notifyOnExit = YES;
[_locationManager startMonitoringForRegion:targetRegion];
[_locationManager startUpdatingLocation];
But it's not sending nothing to delegate. Beacons is working.
If I call just
[_locationManager startRangingBeaconsInRegion:region];
Applications finds all my beacons around me.
Should I just call second method or I'm incorrect?
Have you any suggestions?
The most likely explanation is that you are not waiting long enough to get a call to your delegate. When you are NOT ranging, it takes up to 15 minutes to get callbacks to the delegate methods below. When you are ranging, it takes only 1 second.
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
If you wait the 15 minutes, I suspect you will get the callbacks as expected. The reason it is so much faster when you are ranging is because iOS does constant bluetooth scans when ranging is enabled to look for iBeacons. When you are not ranging, it slows down these scans to save battery. See more info in the blog posts below:
http://developer.radiusnetworks.com/2013/11/13/ibeacon-monitoring-in-the-background-and-foreground.html
http://developer.radiusnetworks.com/2014/03/12/ios7-1-background-detection-times.html
I'm trying to wake up my app (relaunch it) when it enters my defined beacon region but I just can't get it to work. This are the steps and code I'm using.
Set "Location updates" Background Mode to YES.
Monitor my CLBeaconRegion
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:#"EBEFD083-70A2-47C8-9837-E7B5634DF524"];
beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:#"daRegion"];
beaconRegion.notifyEntryStateOnDisplay = NO;
beaconRegion.notifyOnEntry = YES;
beaconRegion.notifyOnExit = YES;
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startMonitoringForRegion:beaconRegion];
Implement delegate methods
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region;
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region;
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region;
Anything that I might be missing? I have read the documentation, blog posts, forums and nothing seems to work. This is one of the websites I read, and this is the other.
The comment "I just can't get it working when the app is killed" is critical.
If you use the iOS7 app switcher to kill an app (e.g. by swiping up on the app icon), then you will not be able to re-launch the app in the background upon entering or leaving an iBeacon region. This is by design -- if the user doesn't want the app running, then Apple thinks code should not be able to make it re-launch. See this thread.
Fortunately, users don't typically do this. For testing purposes, if you want to completely stop an app, don't do this. Reboot your phone instead. (Note, however, that it takes a minute or so after boot before you can detect iBeacons.)
EDIT 2014/03/10: This behavior has changed as of the release of iOS 7.1. Killing an app from the task switcher no longer stops it from detecting iBeacons in the background.