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
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:
- In one case, iPad mini & iPhone 4 stops updating location after several minutes when app is not running and the little arrow become gray.
- 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?