We are doing some work with beacons and iOS and investigating the options for applying a debounce between when we first go in range of the beacon and when we raise a notification to the user.
So the flow would be,
Go in range of a beacon.
Start a timer.
Timer expires, post notification.
Their doesn't seem to be any complications doing this when the app is in the foreground but I'm not sure how this will work with the app closed.
If we register the location background mode then the app will get started when we go in range of a beacon but its unclear to me how long the app we'll be allowed to run. If the app is only allowed to run for a very short period of time then we won't have time to start a timer and have it expire.
Can anyone shed any light on how much execution time the location background mode allows an app when started in the background like this?
I would suggest that once you enter the beacon region that you start ranging the beacon - this will provide you with ranging callbacks at the rate of one per second - even if your app is in the background. You can count these events and use that to determine when you will post the notification. At this point you can stop ranging the beacon.
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
[manager startRangingBeaconsInRegion:(CLBeaconRegion *)region];
self.beaconCount=0;
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*)region];
}
- (void)locationManager:(CLLocationManager *)manager
didRangeBeacons:(NSArray *)beacons
inRegion:(CLBeaconRegion *)region {
self.beaconCount++;
if (self.beaconCount > SOME_THRESHOLD) {
[manager stopRangingBeaconsInRegion:region];
[self postLocalNotification];
}
}
Because this is using the location background mode there is no overall limit to the amount of background time available, but there is a limit on each invocation of a delegate method of a few seconds - but this code won't be anywhere near that.
You can use local notifications for this purpose. There is no guarantee how long your app will continue running in background. If you want to know how much time the OS gives you, you can check UIApplication's backgroundTimeRemaining property.
https://developer.apple.com/library/ios/documentation/uikit/reference/UIApplication_Class/index.html#//apple_ref/occ/instp/UIApplication/backgroundTimeRemaining
Related
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];
I am accessing Gelo iBeacon,Its working fine in foreground mode but how to get ibeacon range or region monitoring when the app is in background mode.
Thanks in advance
Generally iOS lets you monitor for beacons in the background but not range for them. Ranging only works in the background for a few seconds after a monitoring event.
Details on detection times and steps needed to set up background monitoring are here.
Use the following method it will work for background as well as background
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
You need to make sure you ask for always access to location manager.
if ([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)])
{
[self.locationManager requestAlwaysAuthorization];
}
DO NOT use "Location Updates" or "Uses Bluetooth LE accessory" as this might result in rejection from the app store. We are not using ranging, but just monitoring, and this worked fine for us.
Is there a way to define a geofence (center and radius) around the current location of an iOS device, and have the system trigger a callback function in my app when the device exits the geofence? Will this mechanism can wake up a closed app?
I would like to avoid extensive GPS usage, so I would prefer a system message over periodic GPS polling, even at the price of reduced accuracy.
Your solution is Region Monitoring.
In iOS, regions associated with your app are tracked at all times,
including when your app is not running. If a region boundary is
crossed while an app is not running, that app is relaunched into
the background to handle the event. Similarly, if the app is suspended
when the event occurs, it is woken up and given a short amount of
time (around 10 seconds) to handle the event.
Whenever an app request for region monitoring, the iOS takes the stand then. Your app registers some location and asks iOS to monitor the region & notify as on entering or exiting the region with precise accuracy.
Like
CLRegion *region = [[CLCircularRegion alloc] initWithCenter:[location coordinate] radius:250.0 identifier:[[NSUUID UUID] UUIDString]];
Now iOS takes this request & add it to the system queues of region monitoring with an internal identification to your app. As soon as the device enters the region or exits the region, the iOS send a notification to app to rise up & fire the delegate.
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
Please note, if your app is running in the background, the iOS will make your app start in background, once the device enters / exists the registered region.
It is one of the key point on how FourSquare & other similar apps trie to perform much of location data collection & sending it to server & giving user a Tailored message within a small amount of time.
Apple documentation for Region Monitoring
Perfect tutorial which teaches you to build a geo fence step by step in ios
Following are the delegate methods which are triggered when a user enters and exits a region!
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"Welcome to %#", region.identifier);
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"Bye bye");
}
-(void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
NSLog(#"Now monitoring for %#", region.identifier);
}
I'm doing some test using estimote beacons and regions.
Reading the documentation as I start monitoring a region I'm going to tell my delegate that a specific region is being monitored, however, didEnterRegion and didExitRegion are never fired.
My delegate fires:
-(void)beaconManager:(ESTBeaconManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(ESTBeaconRegion *)region
and
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
I didn't check if an error occurs into locationManager:didFailWithError:
At the moment I'm using the state to manually run two private methods that work as didEnter and didExit - in this case I'm also able to monitor the region but I didn't get why locationManager:didEnterRegion: and locationManager:didExitRegion: are never called.
Is someone facing the same behavior?
There are several items to check. The one that addressed my problem was Enabling Background App Refresh in your device's settings (see screenshot below). I had this disabled as a battery saving measure. Monitoring will not work if this is disabled.
If this doesn't address your problem there is a great post you can read that details all of the items to troubleshoot.
iBeacon StartMonitoringForRegion Doesn’t Work
This should work. I would like to see the rest of your code that sets up the monitoring, and I would put NSLog statements in your didEnterRegion didExitRegion and didDetermineState (log the state value, too).
It could be that iOS thinks no region transitions are occurring -- that you are always "in" the region, thus no transition. Transitions can take up to 15 min to occur in the background (although if you are ranging in the foreground it should be within 4 secs for an "out" and 1 sec for an "in". See http://developer.radiusnetworks.com/2013/11/13/ibeacon-monitoring-in-the-background-and-foreground.html
You can force an in/out region callback by setting region.notifyEntryStateOnDisplay=YES; on your monitored region, then hit the shoulder button. This should force monitoring callback. If you do this and don't see the log statements I suggested adding above, I would be very surprised.
I've tried almost everything and it didn't work.
Then I simply restarted my iPhone...and it started working. Incredible.
I had this problem with Estimote Beacons.
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];