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];
});
Related
I am working an application that is sending the device's location to server after every 15mins. The logic is inside didUpdateToLocation i am scheduling a timer to call method restartLocationUpdates after every
180secs(3mins)and stopping the location updates by calling stopLocationDelayBy10Seconds after 10 seconds to save battery.
-(void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
//some bussiness logic goes here....
if(sendLocation){
//send location to server
}
UIApplication* application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTaskId = UIBackgroundTaskInvalid;
if([application respondsToSelector:#selector(beginBackgroundTaskWithExpirationHandler:)]){
bgTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTaskId];
bgTaskId = UIBackgroundTaskInvalid;
}];
//using a timer to schedule a method call
self.bgtimer = [NSTimer scheduledTimerWithTimeInterval:180 target:self
selector:#selector(restartLocationUpdates)
userInfo:nil
repeats:NO];
//stopping the locatioupdates after 10 seconds to save battery
self.delayTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self
selector:#selector(stopLocationDelayBy10Seconds)
userInfo:nil
repeats:NO];
}
- (void) restartLocationUpdates {
counter++;
if(counter == 5){
sendLocation = YES;
counter=0;
}else{
sendLocation = NO;
}
//invalidating the bgtimer before creating and scheduling it again.
if (self.bgtimer) {
[self.bgtimer invalidate];
self.bgtimer = nil;
}
//configuring location manager and then starting location updates
//.....
[locationManager startUpdatingLocation];
}
-(void)stopLocationDelayBy10Seconds{
[locationManager stopUpdatingLocation];
}
Before ios10 it is working fine but after iOS10.3.2 the bgtimer
is not working consistently, it stops working after 10 mins as app goes
to background state. Can someone help me what is wrong with the code
and how to keep running the timer while app is in background.
Imp: Location Services permission is set to 'Always' & BackgroundMode
is enable for location updates & Backgroundfetch
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];
}
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");
}
I am trying to send GPS data information to our server about every 5 minutes, no matter if the app is running or if it's in the background. I can get it to run but it seems to run constantly. I set up a timer to send every 10 seconds for testing but it just keeps sending. I don't think it's the timer that is wrong, I believe locationManager isn't stopping and I don't know why.
This is my code
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"Went to Background");
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[self.locationManager startUpdatingLocation];
self.timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self.locationManager selector:#selector(startUpdatingLocation) userInfo:nil repeats:YES];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
[NSTimer scheduledTimerWithTimeInterval:10 target:self.locationManager selector:#selector(startUpdatingLocation) userInfo:nil repeats:YES];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
if (newLocation.horizontalAccuracy <= 100.0f) {
// Use json and send data to server
...
...
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
self.locationManager.delegate = nil;
}
}
It does the same thing no matter if it's in the background or foreground. Is there something else I need to do to stop locationManager from updating?
To send the location periodically to the server, you want to store and compare a date when updates are received, don't use timers as they are unreliable when the app is in the background.
#implementation
{
NSDate* _lastSentUpdateAt;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"Went to Background");
// Update in 5 minutes.
_lastSentUpdateAt = [NSDate date];
[self.locationManager startUpdatingLocation];
}
// ...
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
// Accuracy is good & 5 minutes have passed.
if (newLocation.horizontalAccuracy <= 100.0f && [_lastSentUpdateAt timeIntervalSinceNow] < -5 * 60) {
// Set date to now
_lastSentUpdateAt = [NSDate date];
// Use json and send data to server
...
...
}
}
#end
I am trying to implement the suggestions given in this post.
Unfortunately the steps are not clear to me. I tried implementing those suggestions, but the backgroundTimeRemaining continues to decrease even after I start and stop locationServices. This is how I developed it:
- (void)applicationDidEnterBackground:(UIApplication *)application {
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
self.timer = nil;
[self initTimer];
});
}
initTimer:
- (void)initTimer {
// Create the location manager if this object does not
// already have one.
if (nil == self.locationManager)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startMonitoringSignificantLocationChanges];
if (self.timer == nil) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3
target:self
selector:#selector(checkUpdates:)
userInfo:nil
repeats:YES];
}
}
checkUpdates:
- (void)checkUpdates:(NSTimer *)timer{
UIApplication* app = [UIApplication sharedApplication];
double remaining = app.backgroundTimeRemaining;
if(remaining < 580.0) {
[self.locationManager startUpdatingLocation];
[self.locationManager stopUpdatingLocation];
[self.locationManager startMonitoringSignificantLocationChanges];
}
DbgLog(#"Reminaing %f", app.backgroundTimeRemaining);
}
Does anyone have a suggestion on what might be wrong in my code? Both initTimer and checkUpdates are being called, but only during for the background execution time (+- 10 Mins). I want the app to update the location every n minutes "forever".
My app's UIBackgroundModes is set to location.
UPDATE:
I am now resetting the timer on didUpdateToLocation and didFailWithError. But still the backgroundTimeRemaining keeps decreasing:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
NSLog(#"Did Update Location = %f / %f", [newLocation coordinate].latitude, [newLocation coordinate].longitude);
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
[self initTimer];
});
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
[self.locationManager stopUpdatingLocation];
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
[self initTimer];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
});
}
I am also invalidating the timer:
- (void)checkUpdates:(NSTimer *)timer{
UIApplication* app = [UIApplication sharedApplication];
double remaining = app.backgroundTimeRemaining;
if(remaining < 580.0 && remaining > 570.0) {
[self.timer invalidate];
self.timer = nil;
[self.locationManager startUpdatingLocation];
[self.locationManager stopUpdatingLocation];
}
DbgLog(#"*************************Checking for updates!!!!!!!!!!! Reminaing %f", app.backgroundTimeRemaining);
}
To anyone else having a nightmare of a time trying to figure this one out, I have a simple solution.
Study the example from raywenderlich.com. The sample code works perfectly, but unfortunately there's no timer during background location. This will run indefinitely.
Add timer by using this code snippet:
-(void)applicationDidEnterBackground {
[self.locationManager stopUpdatingLocation];
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
self.timer = [NSTimer scheduledTimerWithTimeInterval:intervalBackgroundUpdate
target:self.locationManager
selector:#selector(startUpdatingLocation)
userInfo:nil
repeats:YES];
}
Just don't forget to add "App registers for location updates" in info.plist.
After some days trying all possible solutions I was finally able to make it work. Here is what was wrong in my solution:
I need to setup 2 UIBackgroundTaskIdentifiers, one for the timer, and another one for the locationManager
On my solution, just add:
UIApplication *app = [UIApplication sharedApplication];
bgTask2 = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask2];
bgTask2 = UIBackgroundTaskInvalid; }];
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
Once you're in the background you can only have 10 minutes of additional time. There is no way to extend that. You should use the location background services instead.
Working Code(Entire Stepwise Code)
Modified scurioni's code
Step 1
Go to project -> Capabilities -> Background Modes -> select Location updates.
Go to Project -> Info -> add a key NSLocationAlwaysUsageDescription with an optional string.
Step 2
Add this code to AppDelegate.m
#interface AppDelegate ()<CLLocationManagerDelegate>
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (strong, nonatomic) NSTimer *timer;
#end
Step 3
Add This Code in to applicationDidEnterBackground method in AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
UIApplication *app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTaskId =
[app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTaskId];
bgTaskId = UIBackgroundTaskInvalid;
}];
dispatch_async( dispatch_get_main_queue(), ^{
self.timer = nil;
[self initTimer];
[app endBackgroundTask:bgTaskId];
bgTaskId = UIBackgroundTaskInvalid;
});
}
- (void)initTimer {
if (nil == self.locationManager)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager requestAlwaysAuthorization];
[self.locationManager startMonitoringSignificantLocationChanges];
if (self.timer == nil) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3
target:self
selector:#selector(checkUpdates:)
userInfo:nil
repeats:YES];
}
}
- (void)checkUpdates:(NSTimer *)timer{
UIApplication *app = [UIApplication sharedApplication];
double remaining = app.backgroundTimeRemaining;
if(remaining < 580.0) {
[self.locationManager startUpdatingLocation];
[self.locationManager stopUpdatingLocation];
[self.locationManager startMonitoringSignificantLocationChanges];
}
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
NSLog(#"Did Update Location = %f / %f", [newLocation coordinate].latitude, [newLocation coordinate].longitude);
[self updateLocationWithLatitude:[newLocation coordinate].latitude andLongitude:[newLocation coordinate].longitude];
UIApplication* app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask =
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self initTimer];
});
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
[self.locationManager stopUpdatingLocation];
UIApplication *app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask =
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[self initTimer];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task
});
}
-(void)updateLocationWithLatitude:(CLLocationDegrees)latitude
andLongitude:(CLLocationDegrees)longitude{
//Here you can update your web service or back end with new latitude and longitude
}