my app update (1.1) was rejected because of this reason:
We found that your app uses a background mode but does not include functionality that requires that mode to run persistently.
But my app still uses the same functionality in version 1.0.
What i do:
On location update i check if the new location is inside a specific region (rectangle):
- (BOOL)locationInRegion: (CLLocation *) lastLocation {
if ((lastLocation.coordinate.latitude < self.overlayTopLeftCoordinate.latitude && lastLocation.coordinate.latitude > self.overlayBottomLeftCoordinate.latitude) &&
(lastLocation.coordinate.longitude < self.overlayTopRightCoordinate.longitude && lastLocation.coordinate.longitude > self.overlayTopLeftCoordinate.longitude)) {
return YES;
}
return NO;
}
In Foreground and in background mode if the user is in this region i draw a crumb path on a MKMapView. If not, i do nothing.
Require background modes -> app registers for location updates is in my .plist
What i am doing wrong?
I do not have this information in my description:
Continued use of GPS running in the background can dramatically
decrease battery life.
Can this be the (only) reason?
There are 2 possible reasons that your app is rejected.
It is either:-
a) Your app does not need location update when it is in the background.
b) Your app does not handle the background location updates properly
If it is the latter, you will need to handle the location update and also the background tasks when it is in the background. You will need something like this
- (id)init {
if (self==[super init]) {
//Get the share model and also initialize myLocationArray
self.shareModel = [LocationShareModel sharedModel];
self.shareModel.myLocationArray = [[NSMutableArray alloc]init];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
return self;
}
-(void)applicationEnterBackground{
CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
locationManager.distanceFilter = kCLDistanceFilterNone;
[locationManager startUpdatingLocation];
//Use the BackgroundTaskManager to manage all the background Task
self.shareModel.bgTask = [BackgroundTaskManager sharedBackgroundTaskManager];
[self.shareModel.bgTask beginNewBackgroundTask];
}
The above is only a snippet of the complete solution. I have shared a complete solution that is uploaded to Github and also a blog post on how to continuously get location update in the background for iOS 7 here: Background Location Services not working in iOS 7 .
Yes, if you fail to add following warning text in your app description and you have enabled background-location mode, Apple will reject your app.
Continued use of GPS running in the background can dramatically decrease battery life.
Related
We implemented a functionality that tracks the user location on the background at least every 15 minutes. At the moment the background tracking works great for a couple of hours, some cases up until 8 hours, and other cases can go up to 6 days.
The issue is that at a certain point while the user is stationary the app stops managing the location updates for no apparent reason, and reopening the application will not resume the tracking process. We know that the app is not managing the location updates because the device logs show an event from locationd sending the position to our app, but there's no code execution.
We understand that the OS at any time can terminate the application, but we would like to be able to manage those cases to correctly communicate this behavior to the user that expects tracking to keep functioning or for the tracking to continue sending locations in the background.
This issue seems to be inconsistent across our user base, we've identified that users with iOS 14.4+ and from the iPhone 8 up to the iPhone 13 present this problem.
Our current configuration for the location manager is:
AppDelegate.m
#implementation AppDelegate
Location *geoData;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
///...
if (geoData == nil) {
geoData = [Location sharedInstance];
}
///...
}
- (void)applicationWillTerminate:(UIApplication *)application{
geoData = nil;
}
Location.h
#property (nonatomic, retain) CLLocationManager *locationManager;
Location.m
_locationManager = [[CLLocationManager alloc] init];
[_locationManager setDelegate:self];
if ([_locationManager respondsToSelector:#selector(requestAlwaysAuthorization)])
{
[_locationManager requestAlwaysAuthorization];
}
if ([_locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]) {
_locationManager.allowsBackgroundLocationUpdates = YES;
}
_locationManager.pausesLocationUpdatesAutomatically = NO
_locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
_locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[_locationManager startUpdatingLocation];
We receive the updates through the didUpdateLocations where we send the data to an external API server.
The app is configured with Background fetch and Location updates background modes.
I have a location app that needs to get accurate location periodically. Currently I am getting constantly getting location in didUpdateLocation but I only ever log the location every 5 seconds. I am interested in a solution that gets accurate location periodically or on signification change. I would like either or both of these scenarios:
(by very accurate, I need 10m of desired accuracy)
Get a very accurate location every 5 seconds
Notify/callback if user moves a threshold ( eg moves 5 - 10 meters)
The app needs to work when backgrounded as well and location must still be logged if user switches to another app.
I was considering turning on/off location every 5 seconds but was not sure if that is the best practice. I also know there is also allowDeferredLocationUpdatesUntilTraveled but I believe that only applied to backgrounded mode. I would appreciate a solution that saves battery when the app is in use and in background mode. Please share your solutions and best practices for my use case.
I did write an app using Location services, app must send location every 10s. And it worked very well.
Just use the "allowDeferredLocationUpdatesUntilTraveled:timeout" method, following Apple's doc.
Steps are as follows:
Required: Register background mode for update Location.
Create LocationManger and startUpdatingLocation, with accuracy and filteredDistance as whatever you want:
-(void) initLocationManager
{
// Create the manager object
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
_locationManager.delegate = self;
// This is the most important property to set for the manager. It ultimately determines how the manager will
// attempt to acquire location and thus, the amount of power that will be consumed.
_locationManager.desiredAccuracy = 45;
_locationManager.distanceFilter = 100;
// Once configured, the location manager must be "started".
[_locationManager startUpdatingLocation];
}
To keep app run forever using "allowDeferredLocationUpdatesUntilTraveled:timeout" method in background, you must restart updatingLocation with new parameter when app moves to background, like this:
- (void)applicationWillResignActive:(UIApplication *)application {
_isBackgroundMode = YES;
[_locationManager stopUpdatingLocation];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[_locationManager setDistanceFilter:kCLDistanceFilterNone];
_locationManager.pausesLocationUpdatesAutomatically = NO;
_locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[_locationManager startUpdatingLocation];
}
App gets updatedLocations as normal with "locationManager:didUpdateLocations:" callback:
-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// store data
CLLocation *newLocation = [locations lastObject];
self.userLocation = newLocation;
//tell the centralManager that you want to deferred this updatedLocation
if (_isBackgroundMode && !_deferringUpdates)
{
_deferringUpdates = YES;
[self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
}
}
But you should handle the data in then "locationManager:didFinishDeferredUpdatesWithError:" callback for your purpose
- (void) locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {
_deferringUpdates = NO;
//do something
}
NOTE: I think we should reset parameters of LocationManager each time app switches between background/forgeround mode.
Hopefully this should help
When testing with a simple app to test beacon region monitoring I seem to get very inconsistent results depending on the device (not the device model, the specific device). The problem is that I don't receive the CLRegionStateInside state on the region after requestStateForRegion and didEnterRegion does not get called at all on these device. startRangingBeaconsinRegion: works fine but to conserve power and processing it is recommended to only start ranging when the didEnterRegion: method gets called. I tested this on 6 devices and it works on half on them (iPhone 5's) and does't work on one iPhone 5, one 5S and one 4S.
The beacons I use are the kontakt.io beacons.
This is the code to setup the region monitoring
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:BEACON_UUID];
CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
identifier:#"regionIdentifier"];
region.notifyOnEntry = YES;
region.notifyOnExit = YES;
region.notifyEntryStateOnDisplay = YES;
[self.locationManager startMonitoringForRegion:region];
[self.locationManager requestStateForRegion:region];
//If I enable this line, ranging starts on all devices
// [self.locationManager startRangingBeaconsInRegion:region];
I Found the problem. Apparently to use iBeacons in the way that is described in the documents users are required to have the Background Refresh setting enabled in the Settings. To check for this setting I use the following snippet:
if ([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusAvailable) {
NSLog(#"Background updates are available for the app.");
}else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusDenied)
{
NSLog(#"The user explicitly disabled background behavior for this app or for the whole system.");
}else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusRestricted)
{
NSLog(#"Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user.");
}
Found in this answer: Detecting user settings for Background App Refresh in iOS 7
Monitoring may not work for several reasons. One being that App Background Refresh is disabled to save your battery. Another one is neglecting to ensure you have setup the correct App Capabilities.
If this doesn't work there is a great post you can read that details all of the items to troubleshoot.
iBeacon StartMonitoringForRegion Doesn’t Work
I am trying to fetch user locations in foreground & background. I have to call api after I got a locaion update. To work in background I want to use Deferred method. I followed the same process as described in Apple WWDC. I am checking app on iPhone 5 (iOS 7). It is working fine when I am in foreground but did not give me update after I send the app into background. Below is the code which I am using to get location in background.
#import "AppDelegate.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.locationArray = [[NSMutableArray alloc] init];
self.locationErrorArray = [[NSMutableArray alloc] init];
self.manager_loc = [[CLLocationManager alloc] init];
self.manager_loc.activityType = CLActivityTypeFitness;
self.manager_loc.delegate = self;
[self.manager_loc setDesiredAccuracy:kCLLocationAccuracyBest];
[self.manager_loc startUpdatingLocation];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#pragma mark - Location Manager Delgate
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(#"update failed");
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
[self.locationArray addObject:locations];
NSLog(#"udate locations %f %f", manager.location.coordinate.latitude, manager.location.coordinate.longitude);
if (!self.deferredStatus)
{
self.deferredStatus = YES;
[self.manager_loc allowDeferredLocationUpdatesUntilTraveled:100 timeout:30];
}
[self.manager_loc stopUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error
{if (manager.location != nil)
{ [self.locationArray addObject:manager.location];
}
if (error != nil)
{
[self.locationErrorArray addObject:error.description];
}
self.deferredStatus = NO;
NSLog(#"deffered success %f %f", manager.location.coordinate.latitude, manager.location.coordinate.longitude);
}
#end
If I do not stop the location update in didUpdateToLocations Delegate then the location arrow (on status bar) do not go. In that case it gives me locations contionusly. I want location update after a particular time or particualar distance travelled, so that I can hit server with the user locations. Please help me on this.
Use LocationManger distanceFilter Property for update location at particualar distance travelled.
self.manager_loc.distanceFilter= 100;// In meters
If you want location updated in Backggriound then register your for background updates. Youca can do it in plist.
Set location manager to :
if ([self.locationManager respondsToSelector:#selector(pausesLocationUpdatesAutomatically)]) {
self.locationManager.pausesLocationUpdatesAutomatically = NO;
}
Then If you want to location updated after some time or distance then use:
- (void)allowDeferredLocationUpdatesUntilTraveled:(CLLocationDistance)distance
timeout:(NSTimeInterval)timeout // No guaranty it will work exactly or not
If you want location updated based on distance the you can use
Desired accuracty and distanceFilter property.
self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;// Use other accurarcy as your need
self.locationManager.distanceFilter = 3000; //100, 200, etc
If you set activity type to CLActivityTypeFitness all above setting will overrided, And location manager updated according to this activity, which is as per my knowledge will eat Battery.
While using CLLocation Manager one thing you should accept it will not give all updartes 100% accurate.
See my answer for this post: StartUpdateLocations in Background, didUpdatingToLocation only called 10-20 times
If you need location updates in the background under iOS 7, you must call startUpdatingLocation while your App is in the foreground. You can no longer do this while your App is in the background, so you can no longer register for location updates only when you need them and while you need them. You are forced to register for them for the whole time your App is running (in the foreground and the background) and so you’re forced to waste a lot of energy.
You can reduce the battery usage a little bit by setting the accuracy to kCLLocationAccuracyThreeKilometers when you do not need the location updates and set them to kCLLocationAccuracyBest only when you need the updates. But this will nevertheless drain the battery faster than expected.
Please write a bug report to Apple and ask for the „old" behavior of iOS 4,5 and 6, where you could call „startUpdatingLocation“ in the background as well to get location updates in the background. If Apple gets enough requests to change this behavior back to the way it was implemented in iOS 5/6, the more likely it is that Apple will change this back.
The currents situation is really bad. Bad for developers, which are forced to waste energy, or to abandon their Apps, bad for the user, whose device needs to be plugged to a power source much earlier, or who can no longer use certain Apps.
I am having difficulties getting this to work for when the app is not running. I have locationManager:didRangeBeacons:inRegion: implemented and it is called when the app is running in the foreground or background, however it doesn't seem to do anything when I quit the app and lock the screen. The location services icon goes away and I never know that I entered a beacon range. Should the LocalNotification still work?
I have Location updates and Uses Bluetooth LE accessories selected in Background Modes (XCode 5) I didn't think I needed them.
Any help greatly appreciated.
-(void)watchForEvents { // this is called from application:didFinishLaunchingWithOptions
id class = NSClassFromString(#"CLBeaconRegion");
if (!class) {
return;
}
CLBeaconRegion * rflBeacon = [[CLBeaconRegion alloc] initWithProximityUUID:kBeaconUUID identifier:kBeaconString];
rflBeacon.notifyOnEntry = YES;
rflBeacon.notifyOnExit = NO;
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startRangingBeaconsInRegion:rflBeacon];
[self.locationManager startMonitoringForRegion:rflBeacon];
}
-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
if (beacons.count == 0 || eventRanged) { // breakpoint set here for testing
return;
}
eventRanged = YES;
if (backgroundMode) { // this is set in the EnterBackground/Foreground delegate calls
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = [NSString stringWithFormat:#"Welcome to the %# event.",region.identifier];
notification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
// normal processing here...
}
Monitoring can launch an app that isn't running. Ranging cannot.
The key to having monitoring launch your app is to set this poorly documented flag on your CLBeaconRegion: region.notifyEntryStateOnDisplay = YES;
This can launch your app on a region transition even after completely rebooting your phone. But there are a couple of caveats:
Your app launches into the background only for a few seconds. (Try adding NSLog statements to applicationDidEnterBackground and other methods in your AppDelegate to see what is going on.)
iOS can take its own sweet time to decide you entered a CLBeaconRegion. I have seen it take up to four minutes.
As far as ranging goes, even though you can't have ranging wake up your app, you can make your app do both monitoring and ranging simultaneously. If monitoring wakes up your app and puts it into the background for a few seconds, ranging callbacks start up immediately. This gives you a chance to do any quick ranging actions while your app is still running.
EDIT: Further investigation proves that notifyEntryStateOnDisplay has no effect on background monitoring, so the above should work regardless of whether you have this flag. See this detailed explanation and discussion of delays you may experience
Code for iOS 9 to range beacons in the background, by using Location Updates:
Open Project Settings -> Capabilities -> Background Modes -> Toggle Location Updates and Uses Bluetooth LE accessories to ON.
Create a CLLocationManager, request Always monitoring authorization (don't forget to add the Application does not run in background to NO and NSLocationAlwaysUsageDescription in the app's info.plist) and set the following properties:
locationManager!.delegate = self
locationManager!.pausesLocationUpdatesAutomatically = false
locationManager!.allowsBackgroundLocationUpdates = true
Start ranging for beacons and monitoring region:
locationManager!.startMonitoringForRegion(yourBeaconRegion)
locationManager!.startRangingBeaconsInRegion(yourBeaconRegion)
locationManager!.startUpdatingLocation()
// Optionally for notifications
UIApplication.sharedApplication().registerUserNotificationSettings(
UIUserNotificationSettings(forTypes: .Alert, categories: nil))
Implement the CLLocationManagerDelegate and in your didEnterRegion send both startRangingBeaconsInRegion() and startUpdatingLocation() messages (optionally send the notification as well) and set the stopRangingBeaconsInRegion() and stopUpdatingLocation() in didExitRegion
Be aware that this solution works but it is not recommended by Apple due to battery consumption and customer privacy!
More here: https://community.estimote.com/hc/en-us/articles/203914068-Is-it-possible-to-use-beacon-ranging-in-the-background-
Here is the process you need to follow to range in background:
For any CLBeaconRegion always keep monitoring on, in background or foreground and keep notifyEntryStateOnDisplay = YES
notifyEntryStateOnDisplay calls locationManager:didDetermineState:forRegion: in background, so implement this delegate call...
...like this:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
if (state == CLRegionStateInside) {
//Start Ranging
[manager startRangingBeaconsInRegion:region];
}
else{
//Stop Ranging
[manager stopRangingBeaconsInRegion:region];
}
}
I hope this helps.
You are doing two separate operations here - 'ranging' beacons and monitoring for a region. You can monitor for a region in the background, but not range beacons.
Therefore, your implementation of locationManager:didRangeBeacons:inRegion: won't get called in the background. Instead, your call to startMonitoringForRegion will result in one / some of the following methods being called:
– locationManager:didEnterRegion:
– locationManager:didExitRegion:
– locationManager:didDetermineState:forRegion:
These will get called in the background. You can at that point trigger a local notification, as in your original code.
Your app should currently wake up if you're just wanting to be notified when you enter a beacon region. The only background restriction I know of concerns actually hosting an iBeacon on an iOS device. In that case, the app would need to be physically open in the foreground. For that situation, you'd be better off just doing the straight CoreBluetooth CBPeripheralManager implementation. That way you'd have some advertising abilities in the background.