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];
}
Related
After receive a push notification I am trying to get the current localization.
However when I start the code bellow the corelocation fails (didFailWithError) when the app is at background :(
Only works in foreground.
Why?
I already did the .plist for background configuration
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler{
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
NSLog(#" Finalizado background...");
}];
[self handleStartMonitoringLocation];
NSLog(#"Main Thread proceeding...");
}
-(void)handleStartMonitoringLocation{
// location
if (!self.shareModel) {
self.shareModel = [LocationManager sharedManager];
self.shareModel.afterResume = NO;
[self.shareModel addApplicationStatusToPList:#"didFinishLaunchingWithOptions"];
}
[self.shareModel addApplicationStatusToPList:#"applicationDidBecomeActive"];
//Remove the "afterResume" Flag after the app is active again.
self.shareModel.afterResume = NO;
// [self.shareModel startMonitoringLocation];
[self.shareModel startBackgroundLocationUpdates];
}
-(void)startBackgroundLocationUpdates {
// Create a location manager object
self.anotherLocationManager = [[CLLocationManager alloc] init];
// Set the delegate
self.anotherLocationManager.delegate = self;
// Request location authorization
[self.anotherLocationManager requestWhenInUseAuthorization];
// Set an accuracy level. The higher, the better for energy.
self.anotherLocationManager.desiredAccuracy = kCLLocationAccuracyBest;
// Enable automatic pausing
self.anotherLocationManager.pausesLocationUpdatesAutomatically = YES;
// Specify the type of activity your app is currently performing
self.anotherLocationManager.activityType = CLActivityTypeFitness;
if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"9.0")){
// Enable background location updates
self.anotherLocationManager.allowsBackgroundLocationUpdates = YES;
[self.anotherLocationManager requestLocation];
}
else{
// Start location updates
[self.anotherLocationManager startUpdatingLocation];
}
}
#pragma mark - CLLocationManager Delegate
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"locationManager error: %#",error);
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
NSLog(#"locationManager didUpdateLocations: %#",locations);
}
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");
}
}
}
Im building a simple ios app with IBeacon, Im using startMonitoringForRegion for detect beacons.
Thats working ok.
I also want to check the users location when the app is background or closed, for that purpose im using startMonitoringSignificantLocationChanges. In background modes works ok, but when the app is closed the didUpdateLocations: callback is not fired.
My code (from AppDelegate.m) is below:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
[self.locationManager startMonitoringSignificantLocationChanges];
NSString *message = #"UIApplicationLaunchOptionsLocationKey";
[self sendLocalNotificationWithMessage:message];
}
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotif)
{
NSString *idN = [localNotif.userInfo objectForKey:#"id_notif"];
[self sendToDetail:idN];
}
if ([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]) {
NSLog(#"RESPONDS!!!!!");
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound|UIUserNotificationTypeBadge categories:nil]];
}
[self resetBadge];
// Override point for customization after application launch.
NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString:#"B9407F30-F5F8-466E-AFF9-25556B57FE6A"];
NSString *regionIdentifier = #"iBeacons region 1";
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID: beaconUUID identifier: regionIdentifier ];
beaconRegion.notifyEntryStateOnDisplay = YES;
self.locationManager = [[CLLocationManager alloc]init];
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]){
[self.locationManager requestAlwaysAuthorization];
}
self.locationManager.delegate = self;
//self.locationManager.pausesLocationUpdatesAutomatically = NO;
[self.locationManager startMonitoringForRegion:beaconRegion];
return YES;
}
- (void) locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
[manager startRangingBeaconsInRegion:(CLBeaconRegion*) region];
NSLog(#"didEnterRegion");
}
- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*) region];
NSLog(#"didExitRegion");
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"startMonitoringSignificantLocationChanges");
[self.locationManager startMonitoringSignificantLocationChanges];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSLog(#"stopMonitoringSignificantLocationChanges");
[self.locationManager stopMonitoringSignificantLocationChanges];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
NSString *message = #"didUpdateLocations";
[self sendLocalNotificationWithMessage:message];
CLLocation *currentLocation = locations[0];
if (currentLocation != nil) {
NSString *longitude = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
NSString *latitude = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
NSString *message = [NSString stringWithFormat:#"longitud: %# latitud: %#", longitude,latitude ];
[self sendLocalNotificationWithMessage:message];
}
}
Any idea?
Thanks
Specifically, I'm looking at the case where the backgrounded app receives a location services update (significant location change). What exactly happens on the background in this scenario? Can any user code run, including pending timers?
You can do it like below using the background Task and Timer:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}];
self.timer = [NSTimer scheduledTimerWithTimeInterval:60
target:self
selector:#selector(changeAccuracy)
userInfo:nil
repeats:YES];
}
- (void) changeAccuracy {
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[self.locationManager setDistanceFilter:kCLDistanceFilterNone];
}
-(void)locationManager:(CLLocationManager *)lm didUpdateLocations:(NSArray *)locations{
CLLocation *location = [locations lastObject];
NSLog(#"Location returned: %f, %f Accuracy: %f", location.coordinate.latitude, location.coordinate.longitude, location.horizontalAccuracy);
[lm setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
[lm setDistanceFilter:99999];
}
In my project I need to get location evry hour. My code looks like this:
#import "AppDelegate.h"
#implementation AppDelegate {
UIBackgroundTaskIdentifier bgTask;
CLLocationManager *locationManager;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.pausesLocationUpdatesAutomatically = NO;
locationManager.activityType = CLActivityTypeOther;
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
locationManager.distanceFilter = kCLDistanceFilterNone;
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
__block UIBackgroundTaskIdentifier background_task;
background_task = [application beginBackgroundTaskWithExpirationHandler:^ {
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[locationManager startUpdatingLocation];
while(TRUE)
{
NSLog(#"Background time Remaining: %f",[[UIApplication sharedApplication] backgroundTimeRemaining]);
[NSThread sleepForTimeInterval:200]; //wait for x sec
[locationManager startUpdatingLocation];
}
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
});
}
- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *location = [locations lastObject];
NSDate* eventDate = location.timestamp;
NSTimeInterval time = [eventDate timeIntervalSinceNow];
if (abs(time) < 15.0) {
if (location != nil) {
NSString *lat = [NSString stringWithFormat:#"%.8f", location.coordinate.latitude];
NSString *lng = [NSString stringWithFormat:#"%.8f", location.coordinate.longitude];
NSLog(#"location update ...");
//[APIConnection SaveMyPositionWitLat:lat withLng:lng];
//[locationManager stopUpdatingLocation];
}
}
}
This works, but battery im my phone is drained very fast and location is checking every second. If I enable:
[locationManager stopUpdatingLocation];
then location service is stoped permanently. How to change this code for saving battery power ?
Your idea about getting the location is not really possible in iOS since your app needs a active location monitoring to keep running in the background.
For this Apple has the methods to check only major changes, startMonitoringSignificantLocationChanges.
This will inform you app of any major changes in location. Then what you could do to get a more accurate location is start the normal startUpdatingLocation.
This way you save battery and only grab the location when needed.
can you set locationManager.pausesLocationUpdatesAutomatically to YES this should help