Geofencing don't send a notification (iOS) - ios

I'm trying to send a notification every time the user passes a store of my client (about 1700 stores) but it's not working.
Can someone tell me why?
My code:
Store.m
-(CLCircularRegion*)createCircularRegion
{
CLCircularRegion *region=[[CLCircularRegion alloc] initWithCenter:self.geoPoint radius:1000 identifier:self.identifier];
region.notifyOnEntry=YES;
return region;
}
viewController.m
-(void)startMonitoringAllStores
{
if (![CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
NSLog(#"Monitoring is not available for CLCircularRegion class");
}
for (Store *currentStore in self.allStores) {
CLCircularRegion *region=[currentStore createCircularRegion];
[self.locationManager startMonitoringForRegion:region];
}
}
-(void)getStoresFromParse
{
[self findObjectsWithClassName:#"Stores"];
[self.allStores addObjectsFromArray:self.allStores];
[self startMonitoringAllStores];
}
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.locationManager.delegate=self;
[self.locationManager requestAlwaysAuthorization];
return YES;
}
-(void)handleRegionEvent:(CLRegion*)region
{
NSLog(#"Geofence triggered");
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
if ([region isKindOfClass:[CLCircularRegion class]]) {
[self handleRegionEvent:region];
}
}

You can only monitor 20 locations simultaneously in iOS. See Apple's documentation. Also, make sure you are requesting permission via requestAlwaysAuthorization.
One way to get around this limit is to locate the 19 stores closest to the user and monitor those regions, then use the 20th region as an outer boundary around the user's current position whose radius is the distance to the 19th store. When the user crosses the outer boundary, recompute the nearest 19 stores and the boundary, and repeat. In code, you would do something like this. (Note that I have not tested this code; it is just an example.)
- (void)installClosestGeofencesTo:(CLLocation *)currentLocation {
[self removeAllGeofences];
NSArray *closestStores = [self findClosestStoresTo:currentLocation limit:19];
for (Store *store in closestStores) {
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:store.location radius:100 identifier:store.identifier];
[self.locationManager startMonitoringForRegion:region];
}
Store *furthestStore = [closestStores lastObject];
CLCircularRegion *boundaryRegion = [[CLCircularRegion alloc] initWithCenter:currentLocation.coordinate radius:furthestStore.distance identifier:#"boundary"];
[self.locationManager startMonitoringForRegion:boundaryRegion];
}
- (void)removeAllGeofences {
NSArray *monitoredRegions = [self.locationManager monitoredRegions];
for (CLRegion *region in monitoredRegions) {
[self.locationManager stopMonitoringForRegion:region];
}
}
- (NSArray *)findStoresClosestTo:(CLLocation *)location limit:(NSUInteger)limit {
...
}
In your CLLocationManagerDelegate, you would do something like this:
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
if ([region.identifier isEqual:#"boundary"]) {
[self installClosestGeofencesTo:self.locationManager.location];
}
}

Related

Understanding iBeacons in iOS: didDetermineState and didEnterRegion events

Part one:
I have written the following code to monitor iBeacons. I would like to detect the didEnterRegion and didExitRegion event. However it never happens. Would you be able to take a look at the code and suggest what could be missing?
I use the Apple AirLocate sample code to configure one device as iBeacon and perform the following steps to test my code:
Steps:
compile and execute AirLocate sample code on device B
compile and execute this code on device A
in device B use AirLocate app to configure device as iBeacon choosing the following UUID: "74278BDA-B644-4520-8F0C-720EAF059935"
Results:
state inside message
Expected results:
state inside message
did enter region
Why is that?
Those are my plist entries:
Code:
#import "BeaconMonitoring.h"
#implementation BeaconMonitoring
- (instancetype)init
{
self = [super init];
if (self) {
self.locationManager = [[CLLocationManager alloc] init];
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
self.locationManager.delegate = self;
self.locationManager.pausesLocationUpdatesAutomatically = NO;
self.monitoredRegions = [[NSMutableArray alloc] initWithCapacity:10];
}
return self;
}
- (void) startRangingForBeacons{
NSLog(#"in startRangingForBeacons");
[self.locationManager startUpdatingLocation];
[self startMonitoringForRegion:[[NSUUID alloc] initWithUUIDString:#"74278BDA-B644-4520-8F0C-720EAF059935"] :#"b"];
}
- (void) startMonitoringForRegion:(NSUUID*)beaconUUID :(NSString*)regionIdentifier{
/**
Alternatively:
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:#"xxxx"
major:10
minor:20
identifier:#"name"]
**/
// Override point for customization after application launch.
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:beaconUUID identifier:regionIdentifier];
beaconRegion.notifyEntryStateOnDisplay = NO;
beaconRegion.notifyOnEntry = YES;
beaconRegion.notifyOnExit = YES;
[self.locationManager startMonitoringForRegion:beaconRegion];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
[self.monitoredRegions addObject:beaconRegion];
}
- (void) stopRangingForbeacons{
NSLog(#"in stopRangingForbeacons");
[self.locationManager stopUpdatingLocation];
for (int i=0; i < [self.monitoredRegions count]; i++) {
NSObject * object = [self.monitoredRegions objectAtIndex:i];
if ([object isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion * region = (CLBeaconRegion*)object;
[self.locationManager stopMonitoringForRegion:region];
[self.locationManager stopRangingBeaconsInRegion:region];
}
else{
NSLog(#"Serious error, should never happen!");
}
}
}
#pragma CLLocationManagerDelegate
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
[manager startRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager startUpdatingLocation];
NSLog(#"You entered the region.");
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager stopUpdatingLocation];
NSDictionary * notificationData = #{ #"value" : #"exitedRegion"};
[[NSNotificationCenter defaultCenter] postNotificationName:#"dataUpdate" object:nil userInfo:notificationData];
NSLog(#"You exited the region.");
// [self sendLocalNotificationWithMessage:#"You exited the region."];
}
- (void) locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSLog(#"did determine state");
switch (state) {
case CLRegionStateInside:
NSLog(#"state inside");
break;
case CLRegionStateOutside:
NSLog(#"state outside");
break;
case CLRegionStateUnknown:
NSLog(#"state unknown");
break;
default:
NSLog(#"Default case: Region unknown");
break;
}
}
-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
NSLog(#"Did range %lu beacon in region %#", (unsigned long)[beacons count], region.identifier);
NSString * visibleInformation = [NSString stringWithFormat:#"(%lu)", (unsigned long)[beacons count]];
for (int i=0; i<[beacons count]; i++) {
CLBeacon *beacon = [beacons objectAtIndex:i];
if ([beacons count] == 1) {
NSNumber * distance = [NSNumber numberWithFloat:beacon.accuracy];
visibleInformation = [NSString stringWithFormat:#"%i-%i is %f", beacon.major.intValue, beacon.minor.intValue, distance.doubleValue];
}
else{
visibleInformation = [visibleInformation stringByAppendingString:[NSString stringWithFormat:#" %i-%i ", beacon.major.intValue, beacon.minor.intValue]];
}
}
}
#end
Part two:
I had a look at the AirLocate source code to understand if there was something that I had to trigger in the state inside message to get the monitoring working properly. However I found that the ** didDetermineState** method is implemented in the AppDelegate.
Why is that?
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
/*
A user can transition in or out of a region while the application is not running. When this happens CoreLocation will launch the application momentarily, call this delegate method and we will let the user know via a local notification.
*/
UILocalNotification *notification = [[UILocalNotification alloc] init];
if(state == CLRegionStateInside)
{
notification.alertBody = NSLocalizedString(#"You're inside the region", #"");
}
else if(state == CLRegionStateOutside)
{
notification.alertBody = NSLocalizedString(#"You're outside the region", #"");
}
else
{
return;
}
/*
If the application is in the foreground, it will get a callback to application:didReceiveLocalNotification:.
If it's not, iOS will display the notification to the user.
*/
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
Assumption: R = Region made by B and listened to by A
Case 1
IF A was started before B:
app A should tell you determine state for region R = outside
app A should run didEnter Region R
case 2
IF A was infact started after B:
it should only run determineState Region R = inside
the end. There is no 2 here because it never enters the range. it was started inside of it

App crashing (Location GPS Region Monitoring)

App crashes on launch in Xcode: it does not generate a bug report, and instead terminates during a thread (self > UIViewController > _locationManager).
Below is the code from MapViewController.m
#import "MapViewController.h"
#interface MapViewController ()
#end
#implementation MapViewController
- (void)viewDidLoad {
[super viewDidLoad];
//Monitoring a Region
[self.locationManager requestWhenInUseAuthorization];
[self.locationManager startUpdatingLocation];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)initializeLocationServices
{
NSLog(#"Started location services");
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.distanceFilter = kCLDistanceFilterNone;
_locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
_locationManager.pausesLocationUpdatesAutomatically = NO;
[_locationManager startUpdatingLocation]; // to show authorisation popup
}
-(CLCircularRegion*)createRegion
{
// Test coordinates
CLLocationDegrees latitude = 50;
CLLocationDegrees longitude = -1;
CLLocationDistance radius = 50; // meters;
// If radius is too large, registration fails automatically, so limit the radius to the maximum value
if (radius > _locationManager.maximumRegionMonitoringDistance) {
radius = _locationManager.maximumRegionMonitoringDistance;
}
CLCircularRegion* region = [[CLCircularRegion alloc] initWithCenter:CLLocationCoordinate2DMake(latitude, longitude) radius:radius identifier:#"Clue1"];
region.notifyOnEntry = YES;
region.notifyOnExit = YES;
NSLog(#"Created region");
return region;
}
-(void)monitorProximity
{
CLRegion *region = [self createRegion];
// Check if support is unavailable
if ( ![CLLocationManager isMonitoringAvailableForClass:[CLRegion class]]) {
NSLog( #"Failed to initialise region monitoring: support unavailable");
return;
}
// Check if authorised
if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedAlways) {
NSLog( #"Failed to initialise region monitoring: app not authorized to use location services");
return;
} else {
NSLog(#"Started monitoring proximity");
}
// Clear out any old regions to prevent buildup.
if ([_locationManager.monitoredRegions count] > 0) {
for (id obj in _locationManager.monitoredRegions)
[_locationManager stopMonitoringForRegion:obj];
}
[_locationManager startMonitoringForRegion:region];
}
-(void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
NSLog(#"Started monitoring for region: %#", [region description]);
//[_locationManager requestStateForRegion:region]; // check if already inside region
}
-(void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
{
NSLog(#"Failed to start monitoring for region: %#", [error localizedDescription]);
}
-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSLog(#"didDetermineState");
if (state == CLRegionStateInside) {
//println(#"Enter");
NSLog(#"inside");
return;
} else if (state == CLRegionStateOutside) {
// println(#"Outside");
NSLog(#"outside");
} else {
// println("unknown");
NSLog(#"unknown");
}
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"didEnterRegion");
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"didExitRegion");
}
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
NSLog(#"Monitoring authorisation status is now: %#", status == kCLAuthorizationStatusAuthorizedAlways ? #"authorized" : #"not authorized");
if (status == kCLAuthorizationStatusAuthorizedAlways) {
[self monitorProximity];
}
}
#end
UPDATE: Where do I put Initialise call for locationManager and also associate with MapKit map? App won't launch as thread fails.

Why beacons cause Bluetooth to continuously toggle?

I am experiencing a really weird bug working with iOS and iBeacon. I have a really simple BeaconManager that ranges beacons with particular UUID, major and minor values and performs some actions once it found them. My app seems to work properly until it continuously toggle the Bluetooth status and stop doing its job. The only visible result is that the Bluetooth icon in the status bar start flickering due to Bluetooth stopping and restarting.
Where to focus attention?
This is my class definition:
#import "BeaconManager.h"
#implementation BeaconManager
- (instancetype)init {
self = [super init];
if (self) {
NSURL *beep = [[NSBundle mainBundle] URLForResource:#"beep" withExtension:#"aiff"];
soundFileURLRef = (CFURLRef) CFBridgingRetain(beep);
AudioServicesCreateSystemSoundID(soundFileURLRef, &soundFileObject);
// Initializes properties
beacon = [CLBeacon new];
foundBeacons = [NSMutableArray new];
_lastBeaconActionTimes = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)initRegion {
// Initializes the beacon region by giving it an UUID and an identifier
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:BEACON];
beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:#"beacon.region"];
// Starts looking for beacon within the region
[self.locationManager startMonitoringForRegion:beaconRegion];
}
- (void)checkBeacon {
if (!self.locationManager) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
if ([self.locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)])
[self.locationManager requestWhenInUseAuthorization];
}
[self initRegion];
[self locationManager:self.locationManager didStartMonitoringForRegion:beaconRegion];
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[self.locationManager startMonitoringForRegion:beaconRegion];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
[self.locationManager stopRangingBeaconsInRegion:beaconRegion];
}
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSLog(#"Failed monitoring region: %#", error);
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"Location manager failed: %#", error);
}
- (void)locationManager:(CLLocationManager *)manager
didRangeBeacons:(NSArray *)beacons
inRegion:(CLBeaconRegion *)region {
if (foundBeacons.count == 0) {
for (CLBeacon *filterBeacon in beacons) {
// If a beacon is located near the device and its major value is equal to 1000 (MAJOR constant)
if (((filterBeacon.proximity == CLProximityImmediate) || (filterBeacon.proximity == CLProximityNear)))
// Registers the beacon to the list of found beacons
[foundBeacons addObject:filterBeacon];
}
}
// Did some beacon get found?
if (foundBeacons.count > 0) {
// Takes first beacon of the list
beacon = [foundBeacons firstObject];
if (([beacon.major isEqualToNumber:[NSNumber numberWithInt:MAJOR]]) && ([beacon.minor isEqualToNumber:[NSNumber numberWithInt:MINOR]])) {
// Takes the actual date and time
NSDate *now = [[NSDate alloc] init];
NSString *key = [NSString stringWithFormat:#"%# %# %#", [beacon.proximityUUID UUIDString], beacon.major, beacon.minor];
NSDate *lastBeaconActionTime = [_lastBeaconActionTimes objectForKey:key];
if ((lastBeaconActionTime == nil) || ([now timeIntervalSinceDate:lastBeaconActionTime] > MINIMUM_ACTION_INTERVAL_SECONDS)) {
[_lastBeaconActionTimes setObject:now forKey:key];
// Plays beep sound
AudioServicesPlaySystemSound(soundFileObject);
if (self.delegate) {
// Performs actions related to the beacon (i.e. delivers a coupon)
[self.delegate didFoundBeacon:self];
}
self.locationManager = nil;
}
// else [self.locationManager stopMonitoringForRegion:region];
}
[foundBeacons removeObjectAtIndex:0];
beacon = nil;
}
}
#end
Can't say for sure this is the reason why Bluetooth keeps toggling, but this part is definitely suspicious:
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[self.locationManager startMonitoringForRegion:beaconRegion];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
}
This is essentially an infinite loop. Once monitoring starts, iOS invokes the didStartMonitoring method … which starts monitoring for the very same region, which makes the iOS invoke the didStartMonitoring method again, which …
I'd start with removing the startMonitoringForRegion line from this part of your code.

Getting location updates when app is inactive stops after 2 updates

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?
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.

Monitoring overlapping regions

I use region monitoring in ios 6 and 7. It works fine, if regions are not overlapping. But if some regions are overlapping then the app calls delegate method didEnterRegion only for one region.
My code:
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
for (XPLocationModel* locationModel in models) {
if ([self.locationManager respondsToSelector:#selector(startMonitoringForRegion:)]) {
[self.locationManager startMonitoringForRegion:locationModel.region];
} else if ([self.locationManager respondsToSelector:#selector(startMonitoringForRegion:desiredAccuracy:)]) {
[self.locationManager startMonitoringForRegion:locationModel.region desiredAccuracy:XPGeofenceMaster_DesiredAccuracy];
}
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"%s id == %#", __PRETTY_FUNCTION__, region.identifier);
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"%s", __PRETTY_FUNCTION__);
}
If you want, you can use the requestStateForRegion: method to get an update about all of your states, something like this. However, you better be prepared for enter and exit to be called multiple times.
- (void)requestStateForAllLocations
{
for (CLRegion *region in self.locationManager.monitoredRegions) {
[self.locationManager requestStateForRegion:region];
}
}

Resources