7

I have strange problem with updating user's location. Sometimes my app updates location but from time to time breaks updating. I do not know where is problem, my code (from AppDelegate.m) is below:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    NSLog(@"Went to Background");
    // Only monitor significant changes
    [locationManager startMonitoringSignificantLocationChanges];
}


- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Start location services
    locationManager = [[CLLocationManager alloc] init];
    locationManager.desiredAccuracy = kCLLocationAccuracyBest;

    // Only report to location manager if the user has traveled 1000 meters
    locationManager.distanceFilter = 1000.0f;
    locationManager.delegate = self;
    locationManager.activityType = CLActivityTypeAutomotiveNavigation;

    [locationManager stopMonitoringSignificantLocationChanges];
    [locationManager startUpdatingLocation];
}


- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    // Check if running in background or not
    BOOL isInBackground = NO;
    if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
        isInBackground = YES;
    }

    if (isInBackground) {
        // If we're running in the background, run sendBackgroundLocationToServer
        [self sendBackgroundLocationToServer:[locations lastObject]];
    } else {
        // If we're not in the background wait till the GPS is accurate to send it to the server
        if ([[locations lastObject] horizontalAccuracy] < 100.0f) {
            [self sendDataToServer:[locations lastObject]];
        }
    }
}


-(void) sendBackgroundLocationToServer:(CLLocation *)location
{
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    }];

    // Send the data
    [self sendDataToServer:location];

    if (bgTask != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }
}

-(void) sendDataToServer:(CLLocation *)newLocation
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSString *lat = [NSString stringWithFormat:@"%.8f", newLocation.coordinate.latitude];
        NSString *lng = [NSString stringWithFormat:@"%.8f", newLocation.coordinate.longitude];

        [APIConnection SaveMyPositionWitLat:lat withLng:lng];

    });
}

I need to update location every hour or if user change location more then 1000m. I chose the second way in order to save battery but my solution do not work as I need.

I would greatly appreciate any help on this, Thanks in advance!

Paige DePol
  • 1,121
  • 1
  • 9
  • 23
Nizzre
  • 275
  • 1
  • 6
  • 14

1 Answers1

32

I can't say for sure if this is all of you problem but I would make the following changes:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   // Moved to didFinishLaunching... you should not be recreating every time app
   // becomes active. You can check for locations key in the options Dictionary if
   // it is necessary to alter your setup based on background launch

   // Start location services
   locationManager = [[CLLocationManager alloc] init];
   locationManager.desiredAccuracy = kCLLocationAccuracyBest;

   // Only report to location manager if the user has traveled 1000 meters
   locationManager.distanceFilter = 1000.0f;
   locationManager.delegate = self;
   locationManager.activityType = CLActivityTypeAutomotiveNavigation;

   // Start monitoring significant locations here as default, will switch to
   // update locations on enter foreground
   [locationManager startMonitoringSignificantLocationChanges];

   // Hold in property to maintain reference
   self.locationManager = locationManager;
 }

Following your lead, this would be all that remains in your didBecomeActive method (I would use willEnterForeground instead):

- (void)willEnterForeground:(UIApplication *)application
{
   [self.locationManager stopMonitoringSignificantLocationChanges];
   [self.locationManager startUpdatingLocation];
}

Entering into background, go back to significant location changes only:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    NSLog(@"Went to Background");
    // Need to stop regular updates first
    [self.locationManager stopUpdatingLocation];
    // Only monitor significant changes
    [self.locationManager startMonitoringSignificantLocationChanges];
}

In your delegate method, I recommend taking out all the conditional background testing. I am wrapping in background task identifier in case the app is in the background or goes into the background before finish. I am also responding on global thread so we don't block UI (optional):

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{

    UIApplication *app = [UIApplication sharedApplication];
    __block UIBackgroundTaskIdentifier locationUpdateTaskID = [app beginBackgroundTaskWithExpirationHandler:^{
        dispatch_async(dispatch_get_main_queue(), ^{
            if (locationUpdateTaskID != UIBackgroundTaskInvalid) {
                [app endBackgroundTask:locationUpdateTaskID];
                locationUpdateTaskID = UIBackgroundTaskInvalid;
            }
        });
    }];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // You might consider what to do here if you do not ever get a location accurate enough to pass the test.
       // Also consider comparing to previous report and verifying that it is indeed beyond your threshold distance and/or recency. You cannot count on the LM not to repeat.
       if ([[locations lastObject] horizontalAccuracy] < 100.0f) {
            [self sendDataToServer:[locations lastObject]];
       }

        // Close out task Identifier on main queue
        dispatch_async(dispatch_get_main_queue(), ^{
            if (locationUpdateTaskID != UIBackgroundTaskInvalid) {
                [app endBackgroundTask:locationUpdateTaskID];
                locationUpdateTaskID = UIBackgroundTaskInvalid;
            }
        });
    });
}

See my project, TTLocationHandler

on Github for some more examples of using Core Location the way you wish to use it.

Dean Davids
  • 4,174
  • 2
  • 30
  • 44
  • Thx, this solves my issue but what I should change my code for get location every hour ? – Nizzre Feb 24 '14 at 14:07
  • You would think that would require background mode and setting a timer which is entirely possible but I am not sure Apple would approve it. In the interest of saving your users from insane battery use, I will suggest again that you look at that a bit differently. If you have the significant changes concept and region monitoring working, you can keep a reasonable idea of the device location. If they have not moved, no sense in continuously checking. In any case that is another question for which I do have some suggestions in mind. – Dean Davids Feb 24 '14 at 23:01
  • 1
    Yeah, amazing lib! Do you know of any change now with iOS 8? – mllm Oct 12 '14 at 08:06
  • 1
    I am using it in a live app. There is a new requirement for asking the user for permission to allow access to location information. I don't remember the details, I think it was just a matter of adding key in info.plist. I do not think I had to change anything in TTLocationHandler at all. Will verify if I find time. – Dean Davids Oct 12 '14 at 11:10
  • Will `startMonitoringSignificantLocationChanges` works even if the app is terminated/force quit from app launcher? – Bharath Nov 10 '14 at 03:57
  • @TaruniNeema- I know, for sure, this is the case for region monitoring. I believe same for significant location changes. Honestly, though, I haven't had to fiddle with my CL libs for well more than a year or even two. So my memory may fail me here. Easy to test. I'll let you know when I do. – Dean Davids Nov 10 '14 at 22:14
  • I am very impressed with TTLocationHandler. Great work, Dean. – Alex Spencer Nov 29 '14 at 21:47
  • I think it is very important to note that if you want to update the location of the app in the background you must enable the Background Modes capability in your Xcode project (located in the Capabilities tab of your project) and enable the Location updates mode. The code you write to start and stop the standard location services is unchanged. – Alex Spencer Nov 30 '14 at 03:24
  • True, but Apple will reject your app if you do not have a compelling reason for this. You can still accomplish a lot without background mode enabled and if it is possible to do without then they will reject it out of hand. – Dean Davids Nov 30 '14 at 12:34
  • Why are you using locationManager.distanceFilter? From the CLLocaionManager docs: After returning a current location fix, the receiver generates update events only when a significant change in the user’s location is detected. **It does not rely on the value in the distanceFilter** property to generate events. – Alex Zavatone Aug 14 '16 at 02:13
  • 1
    This thread is very old, have not since had the need to go back and refactor any of my TTLocationHandler code. You could be right, it may be redundant and unnecessary. I would already be checking distance in my as a prerequisite for proceeding to use the return value anyway. What it does do is to limit the number of callbacks sent to my delegate so as to avoid unnecessary processing. I haven't kept up with the changes to CLLocation since way back when, there may be best practices that have not been considered. @AlexZavatone Feel free to fork and improve my TTLocationHandler repo if you like. – Dean Davids Aug 14 '16 at 11:44
  • On further review, my answer was only addressing the OP question. His code included the filter setting and I didn't change that. – Dean Davids Aug 14 '16 at 12:10
  • This is very old thread but i think i can ask this , I want to get location in background on every 5 minute , and send Notification on every 30 minute of his current location? – Macl Jul 04 '18 at 09:45
  • Your question would ordinarily be best served as a new question, not a comment here. In this case I think the answer is in the various other related discussions involving CLLocation. The problem with checking in the background in intervals of time is that you need to enable that ability. Apple will deny the app if it does not have a very good reason or if it can be done another way. My library is very old and I haven't updated, but I am still using it untouched in current apps with no trouble. – Dean Davids Jul 04 '18 at 15:59