I took several months developing an application based on iBeacons and I'm really frustrated.
The general idea is that when a beacon is detected, the user is notified with information specific to that iBeacon.
The application was designed as follows, all iBeacons have the same UUID, the Major determines the building (Museum, shop...) and the Minor the specific product (pictures, shoes...). So this application may serve multiple clients.
When the application starts, I begin to do monitoring and ranging for region with our UUID. When the app is in foreground all works perfect. But in background or suspended state the problems starts. Ranging is not allowed on background or suspended state.
I know that the app will be launched into the background for about 5 seconds when you enter or exit the region of a beacon. You can here do ranging in the background for this five second period, after which iOS will suspend your app again.
I managed to extend ranging for up to 3 minutes in the background with the technique learned here. I also get an extra callback with notifyEntryStateOnDisplay = YES;
But this is not enough, if a client enters a region with the app in background or suspended state, he will be notified. And during the extra 3 minutes he will be notified if the ranging detects another iBeacon, but when the 3 minutes background task expired, if no region exit is triggered he will not receive any notification again.
Is there no real solution in a scenario like this? I think it is a very common scenario and I'm surprised no way to deal with it.
EDITED: I tried to find a solution to the problem by monitoring two regions as recommended David Young in his response. In order to obtain more events of entry/exit regions.
I added the code I implemented to try to monitor two regions.
but something I have done incorrectly and didRangeBeacons:InRegion: callback is firing every 10 ms when the expected is every second.
On AppDelegate.m, I'm doing the following inside didFinishLaunchingWithOptions:
[self.locationManager startMonitoringForRegion:self.beaconRegion];
[self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
[self.locationManager startMonitoringForRegion:self.beaconRegion2];
[self.locationManager stopRangingBeaconsInRegion:self.beaconRegion2];
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion2];
Then, on didRangeBeacons:InRegion:
- (void) locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region{
if(beacons.count > 0){
[self.locationManager stopRangingBeaconsInRegion:region];
for (CLBeacon *beacon in beacons){
NSLog(#"beacon detected major: %# minor: %#", beacon.major,beacon.minor);
}
[self.locationManager startRangingBeaconsInRegion:region];
}
}
When I run the application on the simulator, and there is a beacon of each network in range, the message is displayed on the console approximately every 10 ms.
I suspect that the stop and restart the ranging is breaking the expected callback flow, but when only one region is in the range, callbacks occur every second as expected.
The problem you describe is common. The easiest approach is to extend background ranging time forever using a background location mode. (This is similar to what the OP has already done, but for an unlimited period of time.)
Two other options:
Get hardware beacons that allow you to tune down the transmitter power (my company's RadBeacon products allow this) so the transmissions do not overlap. This way you get an exit event then a new enter event as you move from beacon to beacon.
Redesign your identifier schene so the major field is dedicated to identifying up to 20 different regions (based on UUID and major 1-20). You then monitor for all of these regions. Your individual beacons can still use the minor however you want and specifically as the key to trigger messaging. When placing your beacons, you make sure that none with overlapping transmissions share the same major. This will ensure a new background entry event as you move from one to the other.
Beacon ranging in the background works just fine on iOS 8, provided you jump through all the hoops.
Firstly, you need the correct authorization. Add the key NSLocationAlwaysUsageDescription to your Info.plist, and call
[self.locationManager requestAlwaysAuthorization];
Your app must have a Background Mode of Location Updates. Specify in Info.plist or via the Capabilities (sits parallel alongside Build Settings).
Then check for authorisation in applicationDidBecomeActive with
switch([CLLocationManager authorizationStatus])
{
...
Once authorised, you need to do:
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
// If you don't do this, location updates will pause after 15 minutes in the same place
// Usually this is what you want, so I've left this line commented out
// self.locationManager.pausesLocationUpdatesAutomatically = NO;
// Create a NSUUID with the same UUID as the broadcasting beacon
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:kProximityUUID];
_beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
identifier:#"uk.co.airsource.testregion"];
// Necessary to get continued background ranging.
// See https://community.estimote.com/hc/en-us/articles/203914068-Is-it-possible-to-use-beacon-ranging-in-the-background-
_locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
[_locationManager startUpdatingLocation];
[_locationManager startMonitoringForRegion:self.beaconRegion];
Related
I have developed on application which is also developed in android.
I have implemented the beacon region monitoring in iOS as follows.
#pragma mark - Start/Stop Monitoring
- (void)startMonitoring {
[self clearRegionWatch]; // This function removes the already registered monitored regions
NSArray *arrayOfSavedBeacons = [self getSavedBeacons];
if([arrayOfSavedBeacons count]){
for(Beacons *beaconModel in arrayOfSavedBeacons) {
beaconModel.region.notifyOnEntry = YES;
beaconModel.region.notifyOnExit = YES;
beaconModel.region.notifyEntryStateOnDisplay = NO;
NSLog(#"Monitoring start request: %#", [beaconModel dictionaryRepresentation]);
[locationManager startMonitoringForRegion:beaconModel.region];
[locationManager requestStateForRegion:beaconModel.region];
}
}
else{
UIAlertView* curr1=[[UIAlertView alloc] initWithTitle:#"No Beacons" message:#"No Beacon List Found From Server" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[curr1 show];
}
}
Above is the code that starts the monitoring.
Following is the code that i have written for Initialization of Location Manager Instance.
locationManager = [[CLLocationManager alloc] init];
if([locationManager respondsToSelector:#selector(startMonitoringVisits)]) {
//iOS 8.0 onwards
[locationManager startMonitoringVisits];
}
if([locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]) {
//iOS 9.0 onwards
locationManager.allowsBackgroundLocationUpdates = YES;
}
if([locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
//iOS 8.0 onwards
[locationManager requestAlwaysAuthorization];
}
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager setDelegate:self];
[locationManager startUpdatingLocation];
Above code will initialise the location manager when app will start.
I want to get notified for the region entry and exit event.
My issue is my android app can detect the beacon entry from very long distance while iOS app can not detect the region enter or exit from far.
I don't know why this difference is coming?
What i have observed is beacon region monitoring sometime delayed the entry exit notifications by 2 to 3 minutes.
if android can detect the beacon region at particular range then why iOS app can not detect this?(it is significant difference in range form where both app can start to detect the app).
Any advice or suggestion would be helpful.
Thanks.
With iOS CoreLocation, the didEnterRegion monitoring callback is made upon first detection of a beacon matching the region regardless of the signal strength of the beacon advertisement. The callback should fire when the bluetooth chip first sees an advertisement at all, and this should be at a similar range for both iOS and Android devices. While the maximum bluetooth detection range certainly varies between devices, and can be affected by adding a case, putting your phone in your pocket, or by obstructions, in typical use it does not vary a great deal.
A more likely explanation why you are seeing delays in detections is timing, not signal strength. In the background, iOS will defer to a bluetooth chip hardware detection slot to quickly match your beacon region. This is a limited resource, so if these are exhausted, iOS will fall back to periodic software scans to detect, which can take up to 15 minutes. You can confirm this hypothesis by placing your iOS device at the same location where Android first detects, and simply waiting to see if it eventually tests at that distance.
A few tips to make your detections faster:
Uninstall all apps that may monitor beacons on your iOS device, as they may be using up the limited hardware acceleration slots. (Approx. 30 across all apps on the device.)
Don't stop monitoring and restart unless you absolutely have to. This will put your app last in line to get a hardware acceleration detection slot.
Start ranging for beacons when you start monitoring. This won't affect background detection times, but will significantly speed up foreground detection times.
If your app is in the background while you are ranging, it can take up to 15 minutes to get region notification of enter and exists. If your app is running in the foreground ,you should get entered region notifications within a second, and exit region notifications within a few seconds.
It is not beaconSpecific issue but the way of CoreLocation API is implemented in iOS.
I am trying to make an app to track the user GPS all the time, this app is a kind of car GPS tracker to get the location of driver all the time and send it to server.
I have tried to add "location updates" to the "background modes" but the app will automatically suspends after 10 mins when going into background.
Is there a way to make this app run all the time and get the GPS location?
You have two options here:
1) Regular location tracking. This type of tracking works with kCLAuthorizationStatusAuthorizedWhenInUse and kCLAuthorizationStatusAuthorizedAlways authorizations. When CLLocationManager started tracking location once it will receive location updates in delegate method locationManager:didUpdateLocations:. App can go to suspended state, but when location manager receive new location app goes to background state and handles new location in delegate method. How to setup location manager:
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
// Setup location tracker accuracy
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
// Distance filter
self.locationManager.distanceFilter = 50.f;
// Assign location tracker delegate
self.locationManager.delegate = self;
// This setup pauses location manager if location wasn't changed
[self.locationManager setPausesLocationUpdatesAutomatically:YES];
// For iOS9 we have to call this method if we want to receive location updates in background mode
if([self.locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]){
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
[self.locationManager startUpdatingLocation];
}
2) Signification location changes tracking. This type of tracking works only with kCLAuthorizationStatusAuthorizedAlways authorization. It receives new location only each 500 meters, so distance filter and desiredAccuracy don't work here. App can go to suspended state, and even can be terminated by system, but when location updates app goes to background state and receives location in delegate method locationManager:didUpdateLocations:.If app was terminated by system, it will be relaunched in background with UIApplicationLaunchOptionsLocationKey key in launch options in didFinishLaunchingWithOptions app delegate method. How to setup this type on tracking:
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
// Assign location tracker delegate
self.locationManager.delegate = self;
// For iOS9 we have to call this method if we want to receive location updates in background mode
if([self.locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]){
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
[self.locationManager startMonitoringSignificantLocationChanges];
}
You should notice that both of these methods does not guarantee that your application does not go to suspended state.
Also, if app was terminated by user (for example from app switcher by swipe) location tracking in background will not work.
UPDATE (corresponding to comments)
Here is my code examples that work for me:
For Regular tracking. Run the example, provide access to user location, tap Start button to start location updates. To test locations in simulator choose in simulator menu Debug > Location > Freeway Drive. Now you can push app to background by home button (Command+Shift+H). Leave simulator for more than 10 minutes, and all this time app will receive locations. When you return to app you will see red pins on the map.
For Significant changes. Run the app and test by the same way as for previous example.
Monitoring Significant changes can be started only by method [self.locationManager startMonitoringSignificantLocationChanges];
UPDATE (iOS 11)
Changes to location tracking in iOS 11
iOS 11 also makes some major changes to existing APIs. One of the affected areas is location tracking. If your app only uses location while the app is in the foreground, as most apps do, you might not have to change anything at all; however, if it’s one of those apps that continuously track user’s location throughout the day, you should probably book some time this summer for making some changes in how you do the tracking and testing possible usage scenarios.
follow this link: https://mackuba.eu/2017/07/13/changes-to-location-tracking-in-ios-11/
I am sure it is useful for the author because the question was asked in Feb 2016 and I am giving an answer in June 2019. This answer maybe is useful for other users.
Recently, I was working with the same requirement. After 2-3 week hard work, I did it. For other users, I create a helper class for it. Which is available in GitHub.
Please use HSLocationManager for your requirement. I have achieved the same requirements in one of my project
Location manager that allows getting background location updates every
n seconds with desired location accuracy.
Advantage:
OS will never kill our app if the location manager is currently
running.
Give periodically location update when it required(range is between 2 -
170 seconds (limited by max allowed background task time))
Customizable location accuracy and time period.
Low memory consumption(Singleton class)
In reply to comment 1 in the solution (I can't comment anywhere yet): you didn't seem to solve the problem as your app gets suspended and doesn't update the location any more after 10 minutes.
I had the same issue: I had set setAllowsBackgroundLocationUpdates to YES, and I had the NSLocationAlwaysUsageDescription key in my Info.plist, but my App also used to stop tracking location after 10 minutes.
I solved it by adding both NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription to the Info.plist file so it looks like this:
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs to use your location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs to use your location</string>
set it.it take power of battery but your application run in Background.OS does not Suspend your App
[self.locationManager setPausesLocationUpdatesAutomatically:NO];
I have a very strange behavior in my iOS application. While the beacon monitoring is working fine in most cases, there are sometimes hours where the didEnterRegion and didExitRegion events are fired multiple times in a row. The beacon itself is right next to the phone (about 15cm), so there shouldn't be an didExitRegion at all. I know that it is possible to loose a beacon signal what triggers an exit, but it's triggered about 5 times in 3 minutes (both enter and exit - so 10 calls). This is a very rare behavior and seems to be randomly.
The beacons are from Estimote and set via Estimote App to a broadcasting power of -20dBm what should be about 3.5 meters/ 12ft (according to Estimote App) and an advertising interval of 2000ms.
My init of CLLocationManager
if (! _locationManager) {
_locationManager = [[CLLocationManager alloc] init];
if ([_locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
// Not available in iOS 8
_locationManager.allowsBackgroundLocationUpdates = YES;
}
// For iOS 8
if ([_locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[_locationManager requestAlwaysAuthorization];
}
_locationManager.pausesLocationUpdatesAutomatically = NO;
_locationManager.delegate = self;
}
That's how i start monitoring
- (void)startMonitoringForBeaconRegions {
for (CLBeaconRegion *currentBeaconRegion in _beaconRegions) {
//default should be YES
currentBeaconRegion.notifyOnEntry = YES;
currentBeaconRegion.notifyOnExit = YES;
[_locationManager startMonitoringForRegion:currentBeaconRegion];
}
}
Does anyone have a similar behavior with their beacons and maybe a solution ??
Kind regards,
Kyaak
I observed a very occasional similar behavior in my demo/test apps. I experimented with setting beacons to max Tx power and 100 ms advertising interval, and it didn't help, which leads me to believe it might be a bug in Core Location itself. I've seen this erratic behavior with both Estimote SDK (which builds on top of Core Location), but also in pure Core Location.
It ordinarily takes 30 seconds of not "hearing" a beacon for an exit event to trigger, but in situations like these, I was observing enters and exits happening in rapid succession, like it completely ignored the 30-second timer, so I believe this is not actually caused by iOS not being able to "hear" the beacon.
I've also experimented with monitoring for CLCircularRegion instead of CLBeaconRegion (so, a GPS geofence instead of a beacon geofence) set up around my apartment and office, and I've observed the same, sporadic, erratic enters and exits.
Unfortunately, I haven't managed to figure out a fix/workaround for that yet. Maybe if we collectively start opening bug reports with Apple, they'll look into that.
I have an iBeacons app able to range for beacons while in background or not running. I implemented UILocalNotifications and they work fine, meaning i get a notification when i reach the range of a beacon.
Not having a real beacon i created an app (for another device, let's say an iPad for the next scenario) that acts like 2 different beacons, meaning it can broadcast 2 different signals, same UUID but different Major/Minor values (call this beacon A and B), one at a time obviously. My problem is in this scenario:
Have my iPhone (with iBeacons app closed) in lock screen
Activate my iPad app, broadcasting beacon A
My iPhone reacts showing me a notification
I stop iPad app from broadcasting beacon A, wait 1 second, start broadcasting beacon B
My iPhone DOESN'T react
I stop iPad from broadcasting
A few minutes later (about 2) my iPhone shows me the notification of beacon B
Now what i don't understand is this delay, the first time my iPhone reacts immediately, the second time it takes about 2 minutes to notify me the beacon.
If, after beacon B notification, i re-start broadcasting a beacon (A or B) my iPhone reacts immediately, then for the next time it always waits for 2 minutes.
Why is this happening? I've read some article saying that it's because the bluetooth awakes every 2-4 minutes while the app is in background, so i can get the info not faster than this time. But i don't see much sense in this because whenever i get the second notification the broadcasting of the beacon (B in my scenario) was already stopped, it means that if the bluetooth awakes in that very moment no beacon was in the air! But i get the notification, so it means that in someway my iPhone found it before i stopped the broadcasting.
Is this a problem that can be solved?
EDIT with some code
Here is my viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
// Initialize location manager and set ourselves as the delegate and beacons dictionary
_beacons = [[NSMutableDictionary alloc] init];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
// Create a NSUUID with the same UUID as the broadcasting beacon
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:#"6C1AA496-1653-403D-BD1E-7F630AA6F254"];
// Setup a new region with that UUID and same identifier as the broadcasting beacon
self.myBeaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
identifier:#"testregion"];
NSLog(#"startMonitoring");
// Tell location manager to start monitoring for the beacon region
[self.locationManager startMonitoringForRegion:self.myBeaconRegion];
[self.locationManager startRangingBeaconsInRegion:self.myBeaconRegion];
_myBeaconRegion.notifyEntryStateOnDisplay = YES;
// Check if beacon monitoring is available for this device
if (![CLLocationManager isMonitoringAvailableForClass:[CLBeaconRegion class]]){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Monitoring not available" message:nil delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil]; [alert show]; return;
}
}
Now whenever i get a beacon i send a notification, i just wanted to try how it worked so i didn't implemented yet a way to send just 1 notification, it means i get about 9 notification, 1 per sec that's the active time the code can run while in background i suppose (1 second enter region, 9 ranging for beacons)
-(void)locationManager:(CLLocationManager*)manager
didRangeBeacons:(NSArray*)beacons
inRegion:(CLBeaconRegion*)region
{
if([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground){
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = #"Found Beacon";
notification.soundName = #"Default";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
}
Actually to be more specific, if i totally close my app from the multitasking view, or just let it in background, as soon as i start broadcasting a beacon i get the notification**S** (1 second delay). Then stopping the broadcasting and re-play the delay becomes in the order of minutes.
Now for a real scenario, where i should have many beacons in the same place, this delay could be a problem, as long as i get the notification when i could be already far away from the beacons itself.
Has my code any problem? I read those articles but i never found a delay of 15 minutes.
EDIT2 after davidgyoung suggestions
I modified my code and as you said using 2 different regions for beacon A and B the delay is always null. I also logged with the piece of code you gave and i discovered this.
Broadcast the beacon of Region_1
Device shows me the notification of Region_1
Stop broadcasting the beacon of Region_1
The logs say i'm still in the region, only after a couple of minutes i get the log "OUTSIDE Region_1", just now i can re-play the broadcasting to get another notification from Region_1.
So i was curious about this and i read and article http://beekn.net/2014/03/apple-ios-7-1-launches-major-ibeacon-improvement/
The author says that from iOS 7.1 the responsiveness of exiting a region is immediate, actually i'm running 7.1 but i also have a couple of minutes delay. Why this? Did you find the same problem in your tests?
Now, i read that a device can listen for no more than 20 regions right?it means that if i have 100 beacons i can set up just 20 regions and divide these 100 in 20 groups, getting no more than 20 notification (assuming these 100 are in the same place, all in the range of my device) ? That could be a problem because will force the user to run the app in the foreground to get all the information (assuming again each of the 100 beacons have a particular and unit role), am i right?
EDIT: This is my first answer before seeing the code.
I cannot explain this behavior based on your description, but I suspect there may be an issue with the setup that is either delaying your local notification or inaccurately reporting the state of beacon region B when the notification is sent.
Two things would help verify/eliminate this possible cause:
Add NSLog statements in your didDetermineState: forRegion: callback like below, then repeat your test reporting the log results.
// put this in your AppDelegate
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if(state == CLRegionStateInside) {
NSLog(#"locationManager didDetermineState INSIDE for %#", region.identifier);
}
else if(state == CLRegionStateOutside) {
NSLog(#"locationManager didDetermineState OUTSIDE for %#", region.identifier);
}
else {
NSLog(#"locationManager didDetermineState OTHER for %#", region.identifier);
}
}
Post the code that sets up monitoring and issues the notification on detection.
If you have not read this already, you may want to give a quick look to similar tests I have done to measure background detection times:
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
After seeing the code, I think a few issues are going on here:
The code defines only a single region that encompasses BOTH beacon A and beacon B, preventing independent region entry/exit monitoring callbacks, and stopping the phone from getting woken up when switching from transmitting beacon A to beacon B. This is critical, because this means that if you switch from transmitting beacon A to beacon B, iOS will consider itself still in the one region. This can prevent the phone from getting a monitoring event that will wake up the phone in the background.
There is no code in the ranging callback to check which beacon was visible, so it does not seem possible to know for sure which beacon caused the notification. The notification will fire even if the beacon array passed to the ranging callback method is empty (i.e. if no beacons are detected.)
Ranging generally does not work in the background. The only exception is for a few seconds right after entering/exiting a region. I suspect that the two minute delay you report is the time it takes for your iPhone to perform the next background scan for iBeacons (This delay can be 2, 4, or 15 minutes depending on phone state). After that next next scan is performed, a region transition is detected (probably an exit region notification because nothing is transmitting), and ranging starts up for 5 sec firing a notification.
This is all very difficult to test and troubleshoot without directly logging the of the monitoring region callbacks as mentioned in my first answer as well as the ranging callbacks, and logging the identifiers of the beacons detected. Make sure you understand which of these callbacks are firing in order for which beacons, and don't try to troubleshoot too much at once!
In addition to troubleshooting the individual callbacks first, I would also recommend:
Changing the code to have one region per beacon for maximum background responsiveness.
Putting the detected beacon identifiers in the notification so you know beacon caused the notification to fire.
DavidGYoung is correct. iOS7.1 will invoke your app when terminated upon sensing a beacon region traversal. But,and this is critical, iOS will ONLY invoke a terminated app when you ENTER or EXIT a beacon region, and only if your app has previously registered for location events for that particular beacon region.
In your explanation above, it looks like you are only monitoring for ONE beacon region (you did not specify Major/minor numbers so any beacon with the same UUID will qualify). Unfortunately you created two beacon transmitters with the same UUIDs. So when you started the beacon you got ONE notification (ENTER) but had to wait a couple minutes for another (EXIT). typically I've seen exit notifications about 30 sec after a beacon stops transmitting.
If you want to perform the experiment again, try registering for TWO different beacon regions (declare two regions with diff Major/Minor numbers, or make two diff UUIDs), and then use these two diff values in your beacon transmitter. Should work then.
Tom
Testing Device: iPhone 5 (iOS 7)
I have an app that uses RegionMonitoring and updateLocation. If a region is entered, didEnterRegion is called as expected. Then I call startUpdatingLocation. But the method didUpdateToLocation is only called 10-20 times while it should update the location until the timer fires.
The relevant code:
CLLocationManager *_locationManager;
NSTimer *_timer;
-(void)initLocationManager
{
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
[_locationManager setActivityType:CLActivityTypeOther];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBestForNavigation];
[_locationManager setPausesLocationUpdatesAutomatically:NO];
[_locationManager setDistanceFilter:kCLDistanceFilterNone];
}
//Did Enter Region, called as expected:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
[_locationManager startUpdatingLocation];
_timer = [NSTimer scheduledTimerWithTimeInterval:300.0f target:self selector:#selector(scheduleMethod:) userInfo:nil repeats:NO];
}
//Timer Fire Method:
- (void) scheduleMethod:(NSTimer*)timer
{
[Utils writeToLog:#"Timer-Stop"];
[_locationManager stopUpdatingLocation];
}
//This Method only called 10-20 Times (in the first 10-20 Seconds) and not the complete 5 Minutes:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
[Utils writeToLog:#"LocationUpdate!"];
}
So far I tried:
Restarting the Updates in the locationManagerDidPauseLocationUpdates method, but it seems that this is never be called:
-(void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager
{
[WRUtils writeToLog:#"LocationUpdate paused, restarted"];
[_locationManager startUpdatingLocation];
}
Check for errors in the didFailWithError method, but this is not called either.
And played a bit with the properties:
[_locationManager setActivityType:CLActivityTypeOther];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBestForNavigation];
[_locationManager setPausesLocationUpdatesAutomatically:NO];
Plist settings are right I guess:
Required background modes YES
Required background Modes
Item 0 App registers for location updates
How can I solve this?
Apple has introduced a new policy in iOS 7. iOS 7 does no longer deliver location updates in the background if you call „startUpdatingLocation“ while the App is in the background. You only get update when the App is brought to the foreground in this case.
When you’re using the geofencing feature, your App just gets a few seconds each time you get a RegionEntered/Exited notification to process this notification. And in these few seconds you may also get location updates. After these few seconds are over iOS 7 just suspends your App again.
You can use background task to get more than just a few seconds, but in iOS 7 Apple has also reduced the time Apps can run in the background from 10 minutes (iOS 6 and older) to only 3 minutes under iOS 7. It looks like that these 3 minutes are the total amount for the whole time the App is in the background. Which means you can not ask iOS 7 10 times to get 1 minute background time, you’ll only get 3 minutes in total, so after the 3rd time you’ve asked for a minute, your App won't get any background time anymore at all.
Your only chance in getting location updates in the background is to call „startUpdatingLocation“ while the App is in the foreground. This is sad especially when you only need location updates in response to Region(Enter/Exit messages, because you need to let the location updates running all the time. But you can at least reduce the battery usage by setting the accuracy value to kCLLocationAccuracyThreeKilometers when
you do not need location updates and set the accuracy to kCLLocationAccuracyBest only when you really need the geo coordinates. The iOS won’t power up GPS for the kCLLocationAccuracyThreeKilometers value, so the battery usage would be moderate in this case.
Also the value kCLLocationAccuracyBestForNavigation for the accuracy seems to cause problems under IOS 7. I do not get any location updates with this value if the device is not connected to a an external power supply.
All in all the new iOS 7 policy for location updates is making it much harder to develop certain kinds of Apps. Instead of registering for location updates only when needed, you are forced to register for these for the while lifetime of your App. Which of course drains the battery a little bit faster, though Apple’s intension for this new policy was probably the opposite.
UPDATE:
After some more testing I’ve found some kind of solution for the problem.
The docs from Apple mention that when using the Significant Location Change API, Apps can receive location updates in the background even if „startUpdatingLocation“ is started in the background.
My first tests didn’t work well. I was registering my App for significant location updates within the region monitoring delegate methods just before calling startUpdatingLocation (so this location service is only enabled when needed), but this still does not deliver location updates in the background as the docs would suggest.
But if you start listening for significant location changes directly after your App is launched (and never switch this off), you can call start „startUpdatingLocation“ while the App is in the background and also receive location updates in the background. The battery usage of having the "significant location change“ feature on all the time seems to be very low, so this will be probably the solution for most Apps.
You have to check if the „significant location change“ feature is available on the device, but it seems that all current devices do support this. Even the 5th generation of iPod Touch does support it (The iPod Touch can not use cell towers for location updates, which is the base method of this feature according to the docs from Apple, so I guess we can assume that all current devices running iOS 7 can use the „significant location update“ API. Though it’s probably a good idea to check if this feature is really available, maybe there are certain circumstances where the feature is not available).
Using the "significant location change“ API might have one disadvantage: The App can be launched in the background (if it was terminated in the background by the iOS to reuse its memory for other Apps) repeatedly and unnecessarily whenever the device has moved „significantly“ (according to the docs: when the cell tower has changed, but no more than once per 5 min). So Apps which only need to be activated, when a certain region is exited or entered, will be launched and informed about location changed all the time, not only at those regions. But I assume this should be still much better than having standard location updates active all the time.
My iPhone 5s drains the battery only 1% over night with the significant location changes active, instead of 12% with having the standard location updates active with the accuracy set to 3km.
Hope this helps all developers who are struggling with the new iOS 7 behavior.
In background timer will not run, so here your timer object will not respond, You need to create background task handler, check comment of below link,
How to run the timer in background of the application?
In background if you want to continue location service than you need to set pausesLocationUpdatesAutomatically flag, flag info on
pausesLocationUpdatesAutomatically flag info
if ([self.locationManager respondsToSelector:#selector(pausesLocationUpdatesAutomatically)]) {
self.locationManager.pausesLocationUpdatesAutomatically = NO;
}
check my comment on
Location service going to "Inactive" state in iPhone 5
For location manager there below are the CLLocationManagerDelegate methods for location update,
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation [Deprecated in __IPHONE_6_0]
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_6_0);
Where you found
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation ??
Use below code in your didEnterRegion Method to start updates again
[_locationManager startMonitoringSignificantLocationChanges];
if ([_locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)])
{
_locationManager.allowsBackgroundLocationUpdates =YES;
}
[_locationManager startUpdatingLocation];