I'm building an iOS app that works in the background, and posts the location of the user to the server every 3 minutes (as that is the maximum background execution time on iOS 7).
However, there is one problem, the background service terminate at randomly times. So sometimes it can run for 2 hours in the background, sometimes for 7 hours, then 3 hours and randomly so forth.
The code below produces the error.
I have been able to detect when it will terminate, and that is when [UIApplication sharedApplication].backgroundTimeRemaining is below 10 seconds.
Can anyone point in some direction, or explain why it is terminating? My guess is that [self.locationManager startUpdatingLocation] is not 100 % safe? (and the method that makes [UIApplication sharedApplication].backgroundTimeRemaining unlimited?)
Here is the "normal" log I receive on my server every 3 minutes;
iPhone 5: 21:06:45 backgroundTimeRemaining before startUpdatingLocation: 11.014648
iPhone 5: 21:06:45 backgroundTimeRemaining after startUpdatingLocation: 999.000000
iPhone 5: 21:06:48 backgroundTimeRemaining before stopUpdatingLocation: 999.000000
iPhone 5: 21:06:48 backgroundTimeRemaining after stopUpdatingLocation: 999.000000
The code:
#import "BetaLocationController.h"
#import "AppDelegate.h"
#import <Parse/Parse.h>
#import "CommunicationController.h"
#interface BetaLocationController () <CLLocationManagerDelegate>
#property UIBackgroundTaskIdentifier bgTask;
#property (strong, nonatomic) CLLocationManager *locationManager;
#property BOOL locationStarted;
#property (strong, nonatomic) CLLocation *myLocation;
#end
#implementation BetaLocationController
- (id)init
{
self = [super init];
if (self) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.pausesLocationUpdatesAutomatically = NO;
self.locationManager.activityType = CLActivityTypeOther;
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
self.locationManager.distanceFilter = kCLDistanceFilterNone;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation* location = [locations lastObject];
self.myLocation = location;
// NSLog(#"Location: %f, %f", location.coordinate.longitude, location.coordinate.latitude);
}
- (void)didEnterBackground:(NSNotification *) notification
{
[self runBackgroundTask:10];
}
-(void)runBackgroundTask: (int) time{
//check if application is in background mode
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
//create UIBackgroundTaskIdentifier and create tackground task, which starts after time
__block UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// [self runBackgroundTask:5];
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startTrackingBg) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
}
-(void)startTrackingBg{
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
NSDate *now = [NSDate date];
localNotification.fireDate = now;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"HHmm"];
int timeofDay = [[formatter stringFromDate:[NSDate date]] intValue];
localNotification.applicationIconBadgeNumber = timeofDay;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
//set default time
int time = 170;
//if locationManager is ON
if (self.locationStarted == TRUE ) {
//Here I can detect the error
if([UIApplication sharedApplication].backgroundTimeRemaining < 10){
[CommunicationController logToParse:[NSString stringWithFormat:#"%# : Detected error, trying to restart locationservice", [UIDevice currentDevice].name]];
}
//log
[CommunicationController logToParse:[NSString stringWithFormat:#"%#: %# bgTime before stopUpdatingLocation: %f", [UIDevice currentDevice].name, [NSDate date], [self getBackgroundTime]]];
[self.locationManager stopUpdatingLocation];
self.locationStarted = FALSE;
//log
[CommunicationController logToParse:[NSString stringWithFormat:#"%#: %# bgTime after stopUpdatingLocation: %f", [UIDevice currentDevice].name, [NSDate date], [self getBackgroundTime]]];
}else{
//start updating location
//log
[CommunicationController logToParse:[NSString stringWithFormat:#"%#: %# bgTime before startUpdatingLocation: %f", [UIDevice currentDevice].name, [NSDate date], [self getBackgroundTime]]];
[self.locationManager startUpdatingLocation];
self.locationStarted = TRUE;
//Time how long the application will update your location
time = 3;
//log
[CommunicationController logToParse:[NSString stringWithFormat:#"%#: %# bgTime after startUpdatingLocation: %f", [UIDevice currentDevice].name, [NSDate date], [self getBackgroundTime]]];
}
[self runBackgroundTask:time];
}
-(float)getBackgroundTime{
float bgTime = [[UIApplication sharedApplication] backgroundTimeRemaining];
//If bgtime is over 180, it returns unlimited. In iOS 7, only 3 minutes backgroundtime is available
return bgTime > 180 ? 999 : bgTime;
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
[CommunicationController logToParse:[NSString stringWithFormat:#"%#: %# LocationManagerDidFail %#: %f", [UIDevice currentDevice].name, [NSDate date], error, [[UIApplication sharedApplication] backgroundTimeRemaining]]];
NSLog(#"Error in locationManager. It Failed: %#", error);
}
-(void)dealloc {
[CommunicationController logToParse:[NSString stringWithFormat:#"%#: %# got dealloc'ed", [UIDevice currentDevice].name, [NSDate date]]];
NSLog(#"Dealloc in location manager is called");
}
#end
You can in general not be sure your app will be kept alive. A user might want to terminate it actively or the system might be in need of the resources your app is using, and therefore terminate it. I guess you are experiencing this problem due to resource constraints.
You might get around it by using the the Background fetch introduced in iOS7. The OS will at regular interval allow your app to start up again.
Try reading this excellent document describing the feature: http://devstreaming.apple.com/videos/wwdc/2013/204xex2xvpdncz9kdb17lmfooh/204/204.pdf
You can, however, not prevent the user from force your app to close.
You cannot start location services in the background in iOS7. That is why it is inconsistent. You need to start it while the app is in the foreground and leave it on.
Follow these steps to get the locations in the background mode:
Add a "UIBackgroundModes" to a string "locations" key in the info.plist file in your app
Edit/Add your code:
if(self.location==nil){
self.locationManager = [[CLLocationManager alloc] init];
}
[self.locationManager setDelegate:self];
if( [self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)])
{
[self.locationManager requestAlwaysAuthorization];
}
self.locationManager.distanceFilter = 10.0f;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.pausesLocationUpdatesAutomatically=NO;
self.locationManager.activityType=CLActivityTypeAutomotiveNavigation;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
[self.locationManager requestAlwaysAuthorization];
}
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9) {
self.locationManager.allowsBackgroundLocationUpdates = YES;
}
[self.locationManager startUpdatingLocation];
App will move on suspended mode that's why you'll not get the location after that. Please check below URL. it'll help you to get location more then 18 min. even app is in suspended mode.
nice post related to background service
Related
I'm trying to build an iOS where I can update my location every x seconds and send a notification to update the UI.
I get my location but the update is random. Any ideas how to add the interval ?
here is my code :
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *location = [locations lastObject];
[self sendNotification :#"long"
:[NSString stringWithFormat:#"%.8f",location.coordinate.longitude]];
[self sendNotification :#"lat"
:[NSString stringWithFormat:#"%.8f",location.coordinate.latitude]];
}
Try this :
declared in .h file
#property (strong, nonatomic) NSDate *lastTimestamp;
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *mostRecentLocation = locations.lastObject;
NSLog(#"Current location: %# %#", #(mostRecentLocation.coordinate.latitude), #(mostRecentLocation.coordinate.longitude));
NSDate *now = [NSDate date];
NSTimeInterval interval = self.lastTimestamp ? [now timeIntervalSinceDate:self.lastTimestamp] : 0;
if (!self.lastTimestamp || interval >= 5 * 60)
{
self.lastTimestamp = now;
NSLog(#"update your UI");
}
}
use NSTimer:
//declare global
NSTimer *ShareTimeCheck;
int ShareSecLeft;
implement this method:
-(void)shareTimeChecking{
if (ShareSecLeft==0) {
[ShareTimeCheck invalidate];
ShareTimeCheck=nil;
}else{
ShareSecLeft--;
if (ShareSecLeft==0) {
[ShareTimeCheck invalidate];
ShareTimeCheck=nil;
}
}
}
call this one in ur location update method:
if (ShareSecLeft==0) {
[ShareTimeCheck invalidate];
ShareTimeCheck=nil;
ShareSecLeft=5;
heduledTimerWithTimeInterval:1 target:self selector:#selector(shareTimeChecking) userInfo:nil repeats:YES];
//write ur code to update the ui. or update to server as per ur requirement.
}
In iOS natively we can’t change the interval at which the system updates the user location. IOS updates position regularly every second, if have GPS signal.
If the application is in the foreground, we could simply stop monitoring and again start it after interval, for example using NSTimer. In this case, we have to think about the life of the application. The application runs in the background and during idle stops working.
My final procedure is use NSTimer in the background by using UIApplication:beginBackgroundTaskWithExpirationHandler:. This timer triggers periodically while the application runs in the background. You can view the following example:
#import "AppDelegate.h"
#implementation AppDelegate
BOOL locationStarted = FALSE;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//set default value after application starts
locationStarted = FALSE;
//create CLLocationManager variable
locationManager = [[CLLocationManager alloc] init];
//set delegate
locationManager.delegate = self;
app = [UIApplication sharedApplication];
return YES;
}
//update location
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
NSLog(#"Location: %f, %f", newLocation.coordinates.longtitude, newLocation.coordinates.latitude);
}
//run background task
-(void)runBackgroundTask: (int) time{
//check if application is in background mode
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
//create UIBackgroundTaskIdentifier and create a background task, which starts after time
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startTrackingBg) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
}
//starts when application switches to background
- (void)applicationDidEnterBackground:(UIApplication *)application
{
//check if application status is in background
if ( [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
NSLog(#"start background tracking from appdelegate");
//start updating location with location manager
[locationManager startUpdatingLocation];
}
//change locationManager status after time
[self runBackgroundTask:20];
}
//starts with background task
-(void)startTrackingBg{
//write background time remaining
NSLog(#"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);
//set default time
int time = 60;
//if locationManager is ON
if (locationStarted == TRUE ) {
//stop update location
[locationManager stopUpdatingLocation];
locationStarted = FALSE;
}else{
//start updating location
[locationManager startUpdatingLocation];
locationStarted = TRUE;
//ime how long the application will update your location
time = 5;
}
[self runBackgroundTask:time];
}
//application switches back from background
- (void)applicationWillEnterForeground:(UIApplication *)application
{
locationStarted = FALSE;
//stop updating
[locationManager stopUpdatingLocation];
}
/*** other methods ***/
- (void)applicationWillResignActive:(UIApplication *)application{}
- (void)applicationDidBecomeActive:(UIApplication *)application{}
- (void)applicationWillTerminate:(UIApplication *)application{}
#end
A simpler solution might be to run the timer loop:
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startTrackingBg) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
In the below code, i set the fields desiredAccuracy and distanceFilter to 100. But it does not works as it should. As i mention on title, When i put beacon on ipad, didEnterRegion fires, when i take back, after about 15 seconds, didExitRegion fires. Am i missing something?
#import "AppDelegate.h"
#import <CoreLocation/CoreLocation.h>
#import <CoreBluetooth/CoreBluetooth.h>
#interface AppDelegate() <CLLocationManagerDelegate>
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (assign, nonatomic) BOOL dropEmptyRanges;
#end
#implementation AppDelegate
- (instancetype)init
{
if (self = [super init]) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.pausesLocationUpdatesAutomatically = NO;
self.locationManager.distanceFilter = 100;
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
}
return self;
}
-(CLBeaconRegion *) createBeaconRegion: (NSString *) identifier
uuid: (NSString *) uuid
major: (NSInteger) major
minor:(NSInteger) minor
{
NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString:uuid];
unsigned short mj = (unsigned short) major;
unsigned short mi = (unsigned short) minor;
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:beaconUUID major:mj
minor:mi
identifier:identifier];
NSLog(#"createBeaconRegion with: identifier - uuid - major - minor");
beaconRegion.notifyOnEntry = YES;
beaconRegion.notifyOnExit = YES;
beaconRegion.notifyEntryStateOnDisplay = YES;
return beaconRegion;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
CLBeaconRegion *beaconRegion = [self createBeaconRegion:#"backgroundRegion" uuid:#"fda50693-a4e2-4fb1-afcf-c6eb07647825" major:10004 minor:5178];
[self.locationManager startMonitoringForRegion:beaconRegion];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
[self.locationManager startUpdatingLocation];
if ([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]) {
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound|UIUserNotificationTypeBadge categories:nil]];
}
return YES;
}
-(void)locationManager:(CLLocationManager *)manager
didEnterRegion:(CLBeaconRegion *)region {
NSLog(#"Beacon did enter region");
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:3];
localNotification.alertBody = #"Beacon did enter region";
localNotification.timeZone = [NSTimeZone defaultTimeZone];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}
-(void)locationManager:(CLLocationManager *)manager
didExitRegion:(CLBeaconRegion *)region {
NSLog(#"Beacon did exit region");
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:3];
localNotification.alertBody = #"Beacon did exit region";
localNotification.timeZone = [NSTimeZone defaultTimeZone];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}
-(void)locationManager:(CLLocationManager*)manager
didRangeBeacons: (NSArray *) beacons
inRegion:(CLBeaconRegion *)region{
NSLog(#"Beacon did range");
}
#end
Unfortunately, this technique won't work for limiting beacon detections.
The fields locationManager.distanceFilter and locationManager.desiredAccuracy do not apply to beacon detections. These only have any effect on callbacks from CoreLocation when getting location updates based on GPS/Cell/WiFi positioning. The documentation here says as much:
This property is used only in conjunction with the standard location services and is not used when monitoring significant location changes.
While that statement sounds a bit vague, what it means by "standard location services" is when you call startUpdatingLocation to get these non-beacon location updates. You can read more about this here. Although the quote above does not explicitly say so, it would be clearer if it also said "This property is used only in conjunction with the standard location services and is not used when monitoring significant location changes or monitoring beacon regions."
If you want to limit beacon detections based on distance you can not do so with monitoring APIs. You must range for the beacons, get a distance estimate every one second, and only fire your events if you see that the estimated distance is less than your desired threshold.
You're not currently ranging any beacons. Try adding the following lines:
-(void)locationManager:(CLLocationManager *)manager
didEnterRegion:(CLBeaconRegion *)region {
NSLog(#"Beacon did enter region");
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:3];
localNotification.alertBody = #"Beacon did enter region";
localNotification.timeZone = [NSTimeZone defaultTimeZone];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
[manager startRangingBeaconsInRegion:region];
}
-(void)locationManager:(CLLocationManager *)manager
didExitRegion:(CLBeaconRegion *)region {
NSLog(#"Beacon did exit region");
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:3];
localNotification.alertBody = #"Beacon did exit region";
localNotification.timeZone = [NSTimeZone defaultTimeZone];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
[manager stopRangingBeaconsInRegion:region];
}
I have created multiple geo-fence to monitor region entry/exit events.
I have created a location manager in AppDelegate.h file.
#interface AppDelegate : UIResponder <UIApplicationDelegate, CLLocationManagerDelegate>
#property (strong, nonatomic) UIWindow *window;
#property(nonatomic,retain)CLLocationManager *locationManager;
#property(nonatomic,retain)CLLocation *currentLocation;
+(AppDelegate *)sharedDelegate;
AppDelegate.m file
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (notification) {
NSLog(#"AppDelegate didFinishLaunchingWithOptions");
application.applicationIconBadgeNumber = 0;
}
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)])
{
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeAlert|UIUserNotificationTypeSound) categories:nil];
[application registerUserNotificationSettings:settings];
}
else // iOS 7 or earlier
{
UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
[application registerForRemoteNotificationTypes:myTypes];
}
if (!self.locationManager)
{
self.locationManager = [[CLLocationManager alloc] init];
}
self.locationManager.delegate = self;
//locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.distanceFilter = 2.0f;
self.locationManager.activityType = CLActivityTypeAutomotiveNavigation;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
[self.locationManager requestAlwaysAuthorization];
}
if ([self.locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)])
{
self.locationManager.allowsBackgroundLocationUpdates = YES;
}
if ([self.locationManager respondsToSelector:#selector(pausesLocationUpdatesAutomatically)])
{
self.locationManager.pausesLocationUpdatesAutomatically= NO;
}
[self.locationManager stopMonitoringSignificantLocationChanges];
if ([CLLocationManager locationServicesEnabled] && [CLLocationManager authorizationStatus] != kCLAuthorizationStatusDenied)
{
[self.locationManager startUpdatingLocation];
}
// Override point for customization after application launch.
return YES;
}
-(void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
NSLog(#"Started monitoring %# region",region.identifier);
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
NSLog(#"%#",[locations description]);
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
dispatch_async(dispatch_get_main_queue(), ^{
if ([[UIApplication sharedApplication] applicationState]==UIApplicationStateBackground || [[UIApplication sharedApplication] applicationState]==UIApplicationStateInactive)
{
UILocalNotification *localnotification = [[UILocalNotification alloc]init];
localnotification.fireDate=[NSDate dateWithTimeIntervalSinceNow:1];
localnotification.alertBody=#"You are enter in region.";
localnotification.timeZone=[NSTimeZone defaultTimeZone];
localnotification.repeatInterval = 0;
localnotification.hasAction=YES;
[[UIApplication sharedApplication]scheduleLocalNotification:localnotification];
}
else
{
[[[UIAlertView alloc]initWithTitle:#"message" message:#"Enter into region." delegate:self cancelButtonTitle:nil otherButtonTitles:#"Ok ", nil] show];
}
});
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
dispatch_async(dispatch_get_main_queue(), ^{
if ([[UIApplication sharedApplication] applicationState]==UIApplicationStateBackground || [[UIApplication sharedApplication] applicationState]==UIApplicationStateInactive)
{
UILocalNotification *localnotificationExit = [[UILocalNotification alloc]init];
localnotificationExit.fireDate=[NSDate dateWithTimeIntervalSinceNow:1];
localnotificationExit.alertBody=#"You are exit from region.";
NSLog(#"Exit from region.");
localnotificationExit.timeZone=[NSTimeZone defaultTimeZone];
localnotificationExit.repeatInterval = 0;
localnotificationExit.hasAction=YES;
[[UIApplication sharedApplication]scheduleLocalNotification:localnotificationExit];
}
else
{
[[[UIAlertView alloc]initWithTitle:#"message" message:#"Exit from region." delegate:self cancelButtonTitle:nil otherButtonTitles:#"Ok ", nil] show];
}
});
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(#"didFailWithError: %#", error);
[[[UIAlertView alloc] initWithTitle:#"Error" message:#"Failed to Get Your Location" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
}
This things are to manage the region monitoring.
Now my view controller are adding the regions for monitoring.
-(void)AddRegionsInGeoFence
{
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
//----1
CLLocationCoordinate2D centerCoordinate1 = CLLocationCoordinate2DMake(23.046518, 72.543337);
CLCircularRegion *region1 =[[CLCircularRegion alloc] initWithCenter:centerCoordinate1 radius:200 identifier:#"Location First"];
NSLog(#"%#",[region1 description]);
region1.notifyOnEntry=YES;
region1.notifyOnExit=YES;
if (![standardDefaults boolForKey:#"EnterRegion"])
{
[[AppDelegate sharedDelegate].locationManager startMonitoringForRegion:region1];
NSLog(#"Started Monitoring- %#", [region1 description]);
}
[self.mapview setShowsUserLocation:YES];
[self.mapview setUserTrackingMode:MKUserTrackingModeFollow animated:YES];
//----2
CLLocationCoordinate2D centercoordinate2=CLLocationCoordinate2DMake(23.064381, 72.531181);
CLCircularRegion *region2=[[CLCircularRegion alloc]initWithCenter:centercoordinate2 radius:200 identifier:#"Location Second"];
NSLog(#"%#",[region2 description]);
region2.notifyOnEntry=YES;
region2.notifyOnExit=YES;
if (![standardDefaults boolForKey:#"EnterRegion"])
{
[[AppDelegate sharedDelegate].locationManager startMonitoringForRegion:region2];
NSLog(#"Started Monitoring- %#", [region2 description]);
}
//----3
CLLocationCoordinate2D centercoordinate3=CLLocationCoordinate2DMake(23.083583,72.546441);
CLCircularRegion *region3=[[CLCircularRegion alloc]initWithCenter:centercoordinate3 radius:200 identifier:#"Location Third"];
NSLog(#"%#",[region3 description]);
region3.notifyOnEntry=YES;
region3.notifyOnExit=YES;
if (![standardDefaults boolForKey:#"EnterRegion"])
{
[[AppDelegate sharedDelegate].locationManager startMonitoringForRegion:region3];
NSLog(#"Started Monitoring- %#", [region3 description]);
}
//4
CLLocationCoordinate2D centercoordinate4=CLLocationCoordinate2DMake(23.122255, 72.584499);
CLCircularRegion *region4=[[CLCircularRegion alloc]initWithCenter:centercoordinate4 radius:500 identifier:#"Location Fourth"];
NSLog(#"%#",[region4 description]);
region4.notifyOnEntry=YES;
region4.notifyOnExit=YES;
if (![standardDefaults boolForKey:#"EnterRegion"])
{
[[AppDelegate sharedDelegate].locationManager startMonitoringForRegion:region4];
NSLog(#"Started Monitoring- %#", [region4 description]);
[standardDefaults setBool:YES forKey:#"EnterRegion"];
[standardDefaults synchronize];
}
}
My Problem is region monitoring methods are called multiple times even if I am not moving in side the region itself. Everything else is working fine, Accuracy buffer is around 50-80 meters that is fine for me.
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
Also if I am turning off Wi-Fi then it's calling up these methods back to back saying exit from region and enter in to region. As far as I know GPS accuracy is depends on Wi-Fi.
Any help would be highly appreciated.
a Possible workaround in the interim while the apple bug gets fixed is to rate limit the callback; thereby not acting on all the callbacks but limiting the rate at which the callbacks can get processed.
Callback execution portions that happen before the time period expires get ignored.
Here is and example code that could assist, not tested:
The rate is limited to 2 seconds.
-(void)methodRateLimit {
#synchronized(self) {
// rate limit begin
static NSDate *lastTimeExit = nil;
if (!lastTimeExit) {
lastTimeExit = [NSDate distantPast]; // way back in time
}
NSDate *now = [NSDate date];
if ([now timeIntervalSinceDate:lastTimeExit] > 2) {
// do work here
NSLog(#"Executing");
lastTimeExit = now;
} else {
NSLog(#"Limiting");
}
}
}
In my app I want to fetch and upload location on server in regular intervals. for foreground it works fine but for background it has issue as described below. One thing to mention here I use different class for foreground and separate code for background.
To fetch location in background I do the following. It does upload location for about 24 - 40 hours but get stopped after 24-40 hours. I did not get "willTerminate" event. I have also written code to capture global execption so that I could log SigKill if user kill my app but it too does not get logged reason could be we can't catch SigKill.
Now I am stuck don't know what to do to keep my app alive in background to upload location in regular intervals.
//////////////////////////////// didfinish lauch ///////////////////////////////
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
return YES;
}
////////// **did enter background**
- (void) applicationDidEnterBackground:(UIApplication *)application
{
self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}];
timer = [[NSTimer scheduledTimerWithTimeInterval:8*60
target:self
selector:#selector(changeAccuracy)
userInfo:nil
repeats:YES] retain];
[self.locationManager startUpdatingLocation];
}
- (void) changeAccuracy
{
self.bCallbackStarted = NO;
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[self.locationManager setDistanceFilter:100];
}
///////// **did enter fore ground**
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[self.locationManager stopUpdatingLocation];
}
////////// **did update location**
-(void)locationManager:(CLLocationManager *)lm didUpdateLocations:(NSArray *)locations
{
if (self.bCallbackStarted == YES) {
NSString* logStr = [NSString stringWithFormat:#"URLFilteringAppDelegate::didUpdateLocations not uploading location because self.bCallbackStarted == YES"];
LOGI(logStr)
return;
}
self.bCallbackStarted = YES;
[[AppUtility sharedInstance] setLocationStatus:YES];
CLLocation *location = [locations lastObject];
if(location.coordinate.latitude == 0 && location.coordinate.longitude == 0)
{
[UserSettings sharedInstance].Latitude = [NSString stringWithFormat:#"%#", #"Unavailable"];
[UserSettings sharedInstance].Longitude = [NSString stringWithFormat:#"%#", #"Unavailable"];
}
else
{
[UserSettings sharedInstance].Latitude = [NSString stringWithFormat:#"%.8f", location.coordinate.latitude];
[UserSettings sharedInstance].Longitude = [NSString stringWithFormat:#"%.8f", location.coordinate.longitude];
}
[[LocationManager sharedInstance] sendLocation];
/// this is set to save battery
[lm setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
[lm setDistanceFilter:99999];
}
It seems that in iOS 7 an app can not start Location Manager (by calling startUpdatingLocation) from the background task anymore.
In iOS 6 I used approach described here: https://stackoverflow.com/a/6465280 to run background location update every n minutes. The idea was to run background task with a timer and start Location Manager when the timer triggers it. After that turn off Location Manager and start another background task.
After updating to iOS 7 this approach does not work anymore. After starting Location Manager an app does not receive any locationManager:didUpdateLocations. Any ideas?
I found the problem/solution. When it is time to start location service and stop background task, background task should be stopped with a delay (I used 1 second). Otherwise location service wont start. Also Location Service should be left ON for a couple of seconds (in my example it is 3 seconds).
Another important notice, max background time in iOS 7 is now 3 minutes instead of 10 minutes.
Updated on October 29 '16
There is a cocoapod APScheduledLocationManager that allows to get background location updates every n seconds with desired location accuracy.
let manager = APScheduledLocationManager(delegate: self)
manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100)
The repository also contains an example app written in Swift 3.
Updated on May 27 '14
Objective-C example:
1) In ".plist" file set UIBackgroundModes to "location".
2) Create instance of ScheduledLocationManager anywhere you want.
#property (strong, nonatomic) ScheduledLocationManager *slm;
3) Set it up
self.slm = [[ScheduledLocationManager alloc]init];
self.slm.delegate = self;
[self.slm getUserLocationWithInterval:60]; // replace this value with what you want, but it can not be higher than kMaxBGTime
4) Implement delegate methods
-(void)scheduledLocationManageDidFailWithError:(NSError *)error
{
NSLog(#"Error %#",error);
}
-(void)scheduledLocationManageDidUpdateLocations:(NSArray *)locations
{
// You will receive location updates every 60 seconds (value what you set with getUserLocationWithInterval)
// and you will continue to receive location updates for 3 seconds (value of kTimeToGetLocations).
// You can gather and pick most accurate location
NSLog(#"Locations %#",locations);
}
Here is implementation of ScheduledLocationManager:
ScheduledLocationManager.h
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#protocol ScheduledLocationManagerDelegate <NSObject>
-(void)scheduledLocationManageDidFailWithError:(NSError*)error;
-(void)scheduledLocationManageDidUpdateLocations:(NSArray*)locations;
#end
#interface ScheduledLocationManager : NSObject <CLLocationManagerDelegate>
-(void)getUserLocationWithInterval:(int)interval;
#end
ScheduledLocationManager.m
#import "ScheduledLocationManager.h"
int const kMaxBGTime = 170; // 3 min - 10 seconds (as bg task is killed faster)
int const kTimeToGetLocations = 3; // time to wait for locations
#implementation ScheduledLocationManager
{
UIBackgroundTaskIdentifier bgTask;
CLLocationManager *locationManager;
NSTimer *checkLocationTimer;
int checkLocationInterval;
NSTimer *waitForLocationUpdatesTimer;
}
- (id)init
{
self = [super init];
if (self) {
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = kCLDistanceFilterNone;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
}
return self;
}
-(void)getUserLocationWithInterval:(int)interval
{
checkLocationInterval = (interval > kMaxBGTime)? kMaxBGTime : interval;
[locationManager startUpdatingLocation];
}
- (void)timerEvent:(NSTimer*)theTimer
{
[self stopCheckLocationTimer];
[locationManager startUpdatingLocation];
// in iOS 7 we need to stop background task with delay, otherwise location service won't start
[self performSelector:#selector(stopBackgroundTask) withObject:nil afterDelay:1];
}
-(void)startCheckLocationTimer
{
[self stopCheckLocationTimer];
checkLocationTimer = [NSTimer scheduledTimerWithTimeInterval:checkLocationInterval target:self selector:#selector(timerEvent:) userInfo:NULL repeats:NO];
}
-(void)stopCheckLocationTimer
{
if(checkLocationTimer){
[checkLocationTimer invalidate];
checkLocationTimer=nil;
}
}
-(void)startBackgroundTask
{
[self stopBackgroundTask];
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
//in case bg task is killed faster than expected, try to start Location Service
[self timerEvent:checkLocationTimer];
}];
}
-(void)stopBackgroundTask
{
if(bgTask!=UIBackgroundTaskInvalid){
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
-(void)stopWaitForLocationUpdatesTimer
{
if(waitForLocationUpdatesTimer){
[waitForLocationUpdatesTimer invalidate];
waitForLocationUpdatesTimer =nil;
}
}
-(void)startWaitForLocationUpdatesTimer
{
[self stopWaitForLocationUpdatesTimer];
waitForLocationUpdatesTimer = [NSTimer scheduledTimerWithTimeInterval:kTimeToGetLocations target:self selector:#selector(waitForLoactions:) userInfo:NULL repeats:NO];
}
- (void)waitForLoactions:(NSTimer*)theTimer
{
[self stopWaitForLocationUpdatesTimer];
if(([[UIApplication sharedApplication ]applicationState]==UIApplicationStateBackground ||
[[UIApplication sharedApplication ]applicationState]==UIApplicationStateInactive) &&
bgTask==UIBackgroundTaskInvalid){
[self startBackgroundTask];
}
[self startCheckLocationTimer];
[locationManager stopUpdatingLocation];
}
#pragma mark - CLLocationManagerDelegate methods
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
if(checkLocationTimer){
//sometimes it happens that location manager does not stop even after stopUpdationLocations
return;
}
if (self.delegate && [self.delegate respondsToSelector:#selector(scheduledLocationManageDidUpdateLocations:)]) {
[self.delegate scheduledLocationManageDidUpdateLocations:locations];
}
if(waitForLocationUpdatesTimer==nil){
[self startWaitForLocationUpdatesTimer];
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
if (self.delegate && [self.delegate respondsToSelector:#selector(scheduledLocationManageDidFailWithError:)]) {
[self.delegate scheduledLocationManageDidFailWithError:error];
}
}
#pragma mark - UIAplicatin notifications
- (void)applicationDidEnterBackground:(NSNotification *) notification
{
if([self isLocationServiceAvailable]==YES){
[self startBackgroundTask];
}
}
- (void)applicationDidBecomeActive:(NSNotification *) notification
{
[self stopBackgroundTask];
if([self isLocationServiceAvailable]==NO){
NSError *error = [NSError errorWithDomain:#"your.domain" code:1 userInfo:[NSDictionary dictionaryWithObject:#"Authorization status denied" forKey:NSLocalizedDescriptionKey]];
if (self.delegate && [self.delegate respondsToSelector:#selector(scheduledLocationManageDidFailWithError:)]) {
[self.delegate scheduledLocationManageDidFailWithError:error];
}
}
}
#pragma mark - Helpers
-(BOOL)isLocationServiceAvailable
{
if([CLLocationManager locationServicesEnabled]==NO ||
[CLLocationManager authorizationStatus]==kCLAuthorizationStatusDenied ||
[CLLocationManager authorizationStatus]==kCLAuthorizationStatusRestricted){
return NO;
}else{
return YES;
}
}
#end
I tried your method but it didn't work on my side. Can you show me your code?
I actually found a solution to solve the location service problem in iOS 7.
In iOS 7, you can not start the location service in background. If you want the location service to keep running in the background, you have to start it in foreground and it will continue to run in the background.
If you were like me, stop the location service and use timer to re-start it in the background, it will NOT work in iOS 7.
For more detailed information, you can watch the first 8 minutes of video 307 from WWDC 2013: https://developer.apple.com/wwdc/videos/
Update: The location service can work in background as well. Please check Background Location Services not working in iOS 7 for the updated post with complete solution posted on Github and a blog post explaining the details.
Steps to get this implemented are as follows:
Add "App registers for location updates" at item 0 in "Required background modes" in info.plist of your project.
Write below code at application did finish launching.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(startFetchingLocationsContinously) name:START_FETCH_LOCATION object:nil];
Write below code from where you want to start tracking
[[NSNotificationCenter defaultCenter] postNotificationName:START_FETCH_LOCATION object:nil];
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate startUpdatingDataBase];
Paste following code to AppDelegate.m
#pragma mark - Location Update
-(void)startFetchingLocationsContinously{
NSLog(#"start Fetching Locations");
self.locationUtil = [[LocationUtil alloc] init];
[self.locationUtil setDelegate:self];
[self.locationUtil startLocationManager];
}
-(void)locationRecievedSuccesfullyWithNewLocation:(CLLocation*)newLocation oldLocation:(CLLocation*)oldLocation{
NSLog(#"location received successfullly in app delegate for Laitude: %f and Longitude:%f, and Altitude:%f, and Vertical Accuracy: %f",newLocation.coordinate.latitude,newLocation.coordinate.longitude,newLocation.altitude,newLocation.verticalAccuracy);
}
-(void)startUpdatingDataBase{
UIApplication* app = [UIApplication sharedApplication];
bgTask = UIBackgroundTaskInvalid;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^(void){
[app endBackgroundTask:bgTask];
}];
SAVE_LOCATION_TIMER = [NSTimer scheduledTimerWithTimeInterval:300
target:self selector:#selector(startFetchingLocationsContinously) userInfo:nil repeats:YES];
}
Add a class by name "LocationUtil" and paste following code into the header file:
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#protocol LocationRecievedSuccessfully <NSObject>
#optional
-(void)locationRecievedSuccesfullyWithNewLocation:(CLLocation*)newLocation oldLocation:(CLLocation*)oldLocation;
-(void)addressParsedSuccessfully:(id)address;
#end
#interface LocationUtil : NSObject <CLLocationManagerDelegate> {
}
//Properties
#property (nonatomic,strong) id<LocationRecievedSuccessfully> delegate;
-(void)startLocationManager;
And paste following code in LocationUtil.m
-(void)startLocationManager{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[locationManager setPausesLocationUpdatesAutomatically:YES]; //Utkarsh 20sep2013
//[locationManager setActivityType:CLActivityTypeFitness];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
[locationManager startUpdatingLocation];
//Reverse Geocoding.
geoCoder=[[CLGeocoder alloc] init];
//set default values for reverse geo coding.
}
//for iOS<6
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
//call delegate Method
[delegate locationRecievedSuccesfullyWithNewLocation:newLocation oldLocation:oldLocation];
NSLog(#"did Update Location");
}
//for iOS>=6.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *newLocation = [locations objectAtIndex:0];
CLLocation *oldLocation = [locations objectAtIndex:0];
[delegate locationRecievedSuccesfullyWithNewLocation:newLocation oldLocation:oldLocation];
NSLog(#"did Update Locationsssssss");
}