I'm working on the app that monitors CLBeaconRegion and present local notifications to the user when he enter/exits region. Everything works fine, but while testing I spot that if I enter region, got call of locationManager:didEnterRegion: and then turn off transmitting beacon, I got call of locationManager:didExitRegion: only in about 2 minutes.
Could anyone help to avoid this delay?
Transmitting device is iPhone 4s with iOS 8.3. Receiving device is iPhone 4s with iOS 7.1.2
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
[self.locationManager startMonitoringForRegion:beaconRegion];
...
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"%#", NSStringFromSelector(_cmd));
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"%#", NSStringFromSelector(_cmd));
}
Thank you!
When an app is in the background on a 4S (or if it is in the foreground and not ranging) it can take up to 15 minutes to get a didEnterRegion callback. A didExitRegion callback can take 15 minutes and 3 seconds.
Why? Because the 4S only scans for beacons every 15 minutes in the background to save battery. And only after 3 seconds of not seeing a beacon do you get a didExitRegion event.
See here for details: http://developer.radiusnetworks.com/2014/03/12/ios7-1-background-detection-times.html
EDIT: You can make your device detect more quickly in the foreground by enabling ranging at the same as you enable monitoring. Simply call startRangingBeaconsInRegion: after you call startMonitoringBeaconsInRegion:
Newer iOS devices iPhone 5+ have hardware assist that scans constantly in the background and in the foreground when not ranging, but the slots for this assist are limited. Only the first 30 registered regions get access to this fast background detection, and again, this is not available on the 4S.
Related
We are experiencing some strange problems with ranging and monitoring the same beacon region in the same time on iPhone 5S. These problems are not experienced on an iPhone XR so we suspect that it could be some kind of iOS issue on iPhone 5S.
iOS: 12.4.1
Device: iPhone 5S
Xcode: 10.3
Deployment target set to: 12.4
Code: Objective C
Description / steps of the problem:
In a really simple test application we start monitoring and ranging a beacon region (with 2 beacons in proximity) in the same time (code snippet attached below)
We are logging the beacons found by the ranging process (in didRangeBeacons)
The app shows that 2 beacons are detected
Move the app in background, wait a little bit, and bring it into foreground again
The app will show only one beacon (the other one is lost)
Notes:
When our test app detects only 1 beacon instead of 2 beacons, then any other apps on that iPhone 5S that uses ranging will detect just one beacon (this is why we suspect that it should be an iOS issue)
If that beacon region contains only 1 beacon then I can reproduce that issue only if I stop and restart the beacon in the proximity of the iPhone (the ranging process will not see it again). With 2 or more beacons it is reproducible even if the app is moved to background and brought into foreground again as noted above.
Sometimes if I close the screen of the iPhone and re-open it then the lost beacon will re-appear for a moment but it will disappear again
If 'startMonitoringForRegion' is commented out in the code then everything works fine
The above issues are not reproducible on an iPhone XR
Did somebody experienced the above issues and have a good workaround for them? Thank you very much in advance.
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#interface ViewController () <CLLocationManagerDelegate>
#property (nonatomic, strong) CLLocationManager *locationManager;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.allowsBackgroundLocationUpdates = YES;
self.locationManager.pausesLocationUpdatesAutomatically = NO;
[self.locationManager requestAlwaysAuthorization];
CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:#"C336AA38-54BB-483B-AE75-3BA707855035"] identifier:#"test"];
[self.locationManager startMonitoringForRegion:region];
[self.locationManager startRangingBeaconsInRegion:region];
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
}
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray<CLBeacon *> *)beacons inRegion:(CLBeaconRegion *)region {
NSLog(#"%#",beacons);
}
#end
More info:
I'm simulating the 2 beacons, one from an iPad (Noteacon app) and one from a Mac Book Pro (mbeacon tool).
When I start the test app, the beacons show up like this:
(
"CLBeacon (uuid:C336AA38-54BB-483B-AE75-3BA707855035, major:100, minor:101, proximity:2 +/- 1.29m, rssi:-61)",
"CLBeacon (uuid:C336AA38-54BB-483B-AE75-3BA707855035, major:100, minor:102, proximity:2 +/- 2.78m, rssi:-63)"
)
When I move the app to background and bring it back to foreground they will show up like this, one with rssi = 0 for a few seconds and after that it disappears:
(
"CLBeacon (uuid:C336AA38-54BB-483B-AE75-3BA707855035, major:100, minor:102, proximity:0 +/- -1.00m, rssi:0)",
"CLBeacon (uuid:C336AA38-54BB-483B-AE75-3BA707855035, major:100, minor:101, proximity:2 +/- 1.43m, rssi:-62)"
)
(
"CLBeacon (uuid:C336AA38-54BB-483B-AE75-3BA707855035, major:100, minor:101, proximity:2 +/- 1.44m, rssi:-62)"
)
'didExitRegion' is not called in this case, as I still have a beacon in range.
For the second problem, when I have only one beacon, I stop / restart the beacon and it will disappear from the app, and I am not getting the 'didExitRegion' call (I waited several minutes for it). When I close the screen of the phone and reopen it with the app in foreground, usually the beacon re-appers (but sometimes just reappears for a several seconds and disappears again). When a beacon disappears from my app then it will not show up in any apps that ranges beacons (like Locate Beacons app). If I delete my app or comment out the startMonitoringForRegion call then the above problems are not happening and everything works fine.
The interesting thing is that everything works fine on an iPhone XR and couldn't reproduce any of the above problems on it. Only on my iPhone 5S.
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.
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];
It's a further problem based on the question in iBeacon Bluetooth didEnterRegion and didExitRegion methods are never fired which have been solved.
In detail, the method of didEnterRegion and didExitRegion are never fired while the beacon application in the background or the device locked. In addition, these two methods could be triggered normally while the beacon application in the front.
My application is based on the apple demo "Airlocated (sample code, provided by apple inc)" in the bellow link:
https://developer.apple.com/library/ios/samplecode/AirLocate/Introduction/Intro.html#//apple_ref/doc/uid/DTS40013430-Intro-DontLinkElementID_2. I hardly modify any code except adding some code as bellow:
in file "APLAppDelegate.m"
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"Entered region: %#", region);
[self sendLocalNotificationForBeaconRegion:(CLBeaconRegion *)region];
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"Exited region: %#", region);
}
in file APLMonitoringViewcontroller.m
- (void)updateMonitoredRegion
{
...
[self.locationManager startMonitoringForRegion:region];
[self.locationManager startRangeingForRegion:region];
...
}
Complement a bit. I have tried the bellow methods:
Set "Location updates" Background Mode to YES
specify notifyOnExit and notifyOnEntry as true
reboot iPhone4s with iOS 7.1.2
Could anyone give me some suggestions on it?
How long are you waiting for background detections? Under some circumstances, this can take up to 15 minutes. See here for details.
As described in that article, you do not need to set location updates Background Mode to YES, nor do you need to specify notifyOnExit and notifyOnEntry as true.
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.