2

I'm trying to get location updates while my app is inactive (user closed the app). After 2 location updates, the location updates stops launching my app. Indicator for this is the gray arrow in my app in location services settings.

What I'm trying is combination of startMonitoringSignificantLocationChanges & regionMonitoring.

  • I tested on iPhone 4 iOS 7.1.1 and location updates stops after 2 updates.
  • I tested in iPad mini WiFi+Cellular iOS 7.1.1 and location updates stops after 1 update and region monitoring send only 1 location.

Where I'm wrong?

My code:

AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);

    [RegionMonitoringService sharedInstance].launchOptions = launchOptions;
    [[RegionMonitoringService sharedInstance] stopMonitoringAllRegions];

    if ( [CLLocationManager significantLocationChangeMonitoringAvailable] ) {
        [[RegionMonitoringService sharedInstance] startMonitoringSignificantLocationChanges];
    } else {
        NSLog(@"Significant location change service not available.");
    }

    if (launchOptions[UIApplicationLaunchOptionsLocationKey]) {

        [self application:application handleNewLocationEvet:launchOptions]; // Handle new location event

        UIViewController *controller = [[UIViewController alloc] init];
        controller.view.frame = [UIScreen mainScreen].bounds;
        UINavigationController *nvc = [[UINavigationController alloc] initWithRootViewController:controller];

        dispatch_async(dispatch_get_main_queue(), ^{
            appDelegate.window.rootViewController = nvc;
            [appDelegate.window makeKeyAndVisible];
        });

    }
    else {
        // ...
    }

    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{    
    [defaults synchronize];

    [UIApplication sharedApplication].applicationIconBadgeNumber = 0;

    if ([RegionMonitoringService sharedInstance].launchOptions[UIApplicationLaunchOptionsLocationKey]) {
        return;
    }
}    

- (void)application:(UIApplication *)application handleNewLocationEvet:(NSDictionary *)launchOptions
{
    NSLog(@"%s, launchOptions: %@", __PRETTY_FUNCTION__, launchOptions);

    if (![launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) return;
    if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) return;

    SendLocalPushNotification(@"handleNewLocationEvet");
}

RegionMonitoringService.h:

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import "ServerApiManager.h"

@interface RegionMonitoringService : NSObject

@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSDictionary *launchOptions;
@property (strong, nonatomic) NSDate *oldDate;
@property (strong, nonatomic) CLLocation *oldLocation;

+ (RegionMonitoringService *)sharedInstance;
- (void)startMonitoringForRegion:(CLRegion *)region;
- (void)startMonitoringRegionWithCoordinate:(CLLocationCoordinate2D)coordinate andRadius:(CLLocationDirection)radius;
- (void)stopMonitoringAllRegions;
- (void)startMonitoringSignificantLocationChanges;
- (void)stopMonitoringSignificantLocationChanges;
FOUNDATION_EXPORT NSString *NSStringFromCLRegionState(CLRegionState state);

@end

RegionMonitoringService.m:

#import "RegionMonitoringService.h"

static CLLocationDistance const kFixedRadius = 250.0;

@interface RegionMonitoringService () <CLLocationManagerDelegate>
- (NSString *)identifierForCoordinate:(CLLocationCoordinate2D)coordinate;
- (CLLocationDistance)getFixRadius:(CLLocationDistance)radius;
- (void)sortLastLocation:(CLLocation *)lastLocation;
@end

@implementation RegionMonitoringService

+ (RegionMonitoringService *)sharedInstance
{
    static RegionMonitoringService *_sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

- (instancetype)init
{
    self = [super init];
    if (!self) {
        return nil;
    }

    _locationManager = [[CLLocationManager alloc] init];
    _locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
    _locationManager.distanceFilter = kCLDistanceFilterNone;
//    _locationManager.activityType = CLActivityTypeFitness;
    _locationManager.delegate = self;

    return self;
}

- (void)startMonitoringForRegion:(CLRegion *)region
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
    [_locationManager startMonitoringForRegion:region];
}

- (void)startMonitoringRegionWithCoordinate:(CLLocationCoordinate2D)coordinate andRadius:(CLLocationDirection)radius
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

    if (![CLLocationManager regionMonitoringAvailable]) {
        NSLog(@"Warning: Region monitoring not supported on this device.");
        return;
    }

    if (__iOS_6_And_Heigher) {
        CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter:coordinate
                                                                   radius:radius
                                                               identifier:[self identifierForCoordinate:coordinate]];
        [_locationManager startMonitoringForRegion:region];
    }
    else {
        CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:coordinate
                                                                     radius:radius
                                                                 identifier:[self identifierForCoordinate:coordinate]];
        [_locationManager startMonitoringForRegion:region];
    }

    SendLocalPushNotification([NSString stringWithFormat:@"StartMonitor: {%f, %f}", coordinate.latitude, coordinate.longitude]);
}

- (void)stopMonitoringAllRegions
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

    if (_locationManager.monitoredRegions.allObjects.count > 1) {
        for (int i=0; i<_locationManager.monitoredRegions.allObjects.count; i++) {
            if (i == 0) {
                NSLog(@"stop monitor region at index %d", i);
                CLRegion *region = (CLRegion *)_locationManager.monitoredRegions.allObjects[i];
                [_locationManager stopMonitoringForRegion:region];
            }
        }
    }
}

- (void)startMonitoringSignificantLocationChanges
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

    [_locationManager startMonitoringSignificantLocationChanges];
}

- (void)stopMonitoringSignificantLocationChanges
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

     [_locationManager stopMonitoringSignificantLocationChanges];
}

- (NSString *)identifierForCoordinate:(CLLocationCoordinate2D)coordinate
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

    return [NSString stringWithFormat:@"{%f, %f}", coordinate.latitude, coordinate.longitude];
}

FOUNDATION_EXPORT NSString *NSStringFromCLRegionState(CLRegionState state)
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

    if (__iOS_6_And_Heigher) {
        return @"Support only iOS 7 and later.";
    }

    if (state == CLRegionStateUnknown) {
        return @"CLRegionStateUnknown";
    } else if (state == CLRegionStateInside) {
        return @"CLRegionStateInside";
    } else if (state == CLRegionStateOutside) {
        return @"CLRegionStateOutside";
    } else {
        return [NSString stringWithFormat:@"Undeterminded CLRegionState"];
    }
}

- (CLLocationDistance)getFixRadius:(CLLocationDistance)radius
{
    if (radius > _locationManager.maximumRegionMonitoringDistance) {
        radius = _locationManager.maximumRegionMonitoringDistance;
    }
    return radius;
}

- (void)sortLastLocation:(CLLocation *)lastLocation
{
    NSLog(@"%s, %@", __PRETTY_FUNCTION__, lastLocation);

    self.oldDate = lastLocation.timestamp; // Get new date

    NSTimeInterval seconds = fabs([self.oldLocation.timestamp timeIntervalSinceDate:self.oldDate]); // Calculate how seconds passed
    NSInteger minutes = seconds * 60; // Calculate how minutes passed

    if (lastLocation && self.oldLocation) { // New & old location are good
        if ([lastLocation distanceFromLocation:self.oldLocation] >= 200 || minutes >= 30) { // Distance > 200 or 30 minutes passed
            [[ServerApiManager sharedInstance] saveLocation:lastLocation]; // Send location to server
        }
    }
    else { // We just starting location updates
        [[ServerApiManager sharedInstance] saveLocation:lastLocation]; // Send new location to server
    }
    self.oldLocation = lastLocation; // Set old location
}

#pragma mark - CLLocationManagerDelegate Methods

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    NSLog(@"%s, %@", __PRETTY_FUNCTION__, locations);
    CLLocation *lastLocation = (CLLocation *)locations.lastObject;
    CLLocationCoordinate2D coordinate = lastLocation.coordinate;

    if (lastLocation == nil || coordinate.latitude  == 0.0 || coordinate.longitude == 0.0) {
        return;
    }

    [self startMonitoringRegionWithCoordinate:coordinate andRadius:[self getFixRadius:kFixedRadius]];

    [self sortLastLocation:lastLocation];
}

- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
    NSLog(@"%s, currentLocation: %@, regionState: %@, region: %@",
          __PRETTY_FUNCTION__, manager.location, NSStringFromCLRegionState(state), region);
}

- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
    NSLog(@"%s, REGION: %@", __PRETTY_FUNCTION__, region);
    [manager requestStateForRegion:region];
}

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
}

- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
    NSLog(@"%s, REGION: %@", __PRETTY_FUNCTION__, region);

    [self stopMonitoringAllRegions];
    [self startMonitoringRegionWithCoordinate:manager.location.coordinate andRadius:[self getFixRadius:kFixedRadius]];

    CLLocation *lastLocation = manager.location;
    CLLocationCoordinate2D coordinate = lastLocation.coordinate;
    if (lastLocation == nil || coordinate.latitude  == 0.0 || coordinate.longitude == 0.0) {
        return;
    }

    [self sortLastLocation:manager.location];
}

@end

enter image description here

enter image description here

EDIT 1:

I did a a lot of real time tests with car with several devices (iPhone 5s, iPad mini, iPhone 4) after several tests I came to this:

  1. In one case, iPad mini & iPhone 4 stops updating location after several minutes when app is not running and the little arrow become gray.
  2. When WiFi was off, the accuracy was terrible and locations updated rarely.

EDIT 2:

OK, after a lot of driving and walking around and testing it it works like a charm so far. I managed to make it work, combining significantLocationChanges & region monitoring, always register a geofence around my current location and always starting significant location changes when new UIApplicationLaunchOptionsLocationKey come. Note that turning off wifi make accuracy very low and even sometimes not working.

Any bugs in my code?

Idan Moshe
  • 1,675
  • 4
  • 28
  • 65
  • 1
    Just a thought `startMonitoringSignificantLocationChanges` monitors only major changes. Have you tried moving to different places to increase distance ? – Janak Nirmal Jun 18 '14 at 05:48
  • For iOS 7.0, in order to get the location update continuously, you will have to know when to restart the locationManager to make the app always in active mode. I heard that for iOS 7.1, you can get the location update even when it is inactive but I haven't find any reliable way to make it work yet. For iOS 7.0, you can see my solution here: http://stackoverflow.com/questions/18946881/background-location-services-not-working-in-ios-7/21966662#21966662 This solution works well for iOS 7.1 as well but it will drain some battery. – Ricky Jun 18 '14 at 06:06
  • @Ricky Thanks, I'm familiar with your code, but what I need is to keep getting updates when the app is inactive. Just like Moves/Lookout/McAfee etc apps. I thought about combining region monitoring with radius of 250 meters but my code doesn't reliable. – Idan Moshe Jun 18 '14 at 06:13
  • No problem. I am finding the solution to get location update when the app is inactive as well (iOS 7.1 and above). I hope that someone will answer this question with a good solution. I will be watching this thread. – Ricky Jun 18 '14 at 06:17
  • If those apps do it the right way, there must be a good working solution for this, I do hope someone will help. – Idan Moshe Jun 18 '14 at 06:21
  • What about my answer? I ran into similar issues a while ago and noticed that it is due to the timely behaviour of `startMonitoringSignificantLocationChanges`. – nburk Jun 18 '14 at 08:28
  • Do you still have the same issue? – nburk Jun 19 '14 at 07:28

1 Answers1

1

From the Apple docs it can be seen that updates are not sent more frequently than every 5 minutes and for 500 meters of location change:

Apps can expect a notification as soon as the device moves 500 meters or more from its previous notification. It should not expect notifications more frequently than once every five minutes. If the device is able to retrieve data from the network, the location manager is much more likely to deliver notifications in a timely manner.

You are going to receive the updates even when the app is inactive. Another hint for you might be that oyu can test the location in the simulator instead using a real device, this way you don't have to go outside for testing and can still check your logs too. In the simulator menu, chose Debug --> Location.

nburk
  • 22,409
  • 18
  • 87
  • 132
  • Was this helpful? Let me know if you still struggle with the issue! – nburk Jun 18 '14 at 08:27
  • Checking again the distances between last reported coordinate and when I stopped tracking was almost 1 kilometer. – Idan Moshe Jun 18 '14 at 09:03
  • What about the time? You have to wait 5 min until the next location update is received. – nburk Jun 18 '14 at 09:16
  • The best way to debug this is to use the simulator, you can set custom locations there where you can be sure that the changes are significant. – nburk Jun 18 '14 at 09:16
  • Please dont tell me your reactions always take that long because you have to go outside and test :D – nburk Jun 18 '14 at 10:01
  • Basically you're right, I combined region monitoring with 250 meters radius and now I've a much more frequent location updates. – Idan Moshe Jun 19 '14 at 11:23
  • problem with simulator is that it won't take your app into a suspended state. So it's really not a good idea to test it with simulator. Rather test with actual device. And test with device being launched from user tapping not Xcode running. For more see this [post thread written by an Apple employee](https://forums.developer.apple.com/thread/14855) – mfaani Jul 28 '17 at 14:18