My code supposed to do something like this:
Register current location (using significant location changes) as geofence with 200 meters radius using this method:
- (void)startMonitoringRegionWithCoordinate:(CLLocationCoordinate2D)coordinate
When user exit a geofence - register his current location again and send to server
This does not happen well, sometimes it works not as expected and sometimes it works very poor with huge distances.
- I'm trying to figure out what my problem is with the auto registering geofences methods.
- Also I'm trying to figure out, maybe significant location changes mess it up?
I've been trying to changes radius to 100 -250 meters and results are the same, the frequent of the exit from geofence event doesn't happen as expected.
What am I missing?
Code:
- (CLLocationDistance)preferableRadius
{
CLLocationDistance radius = (CLLocationDistance)[defaults doubleForKey:kPREFERABLE_RADIUS_KEY];
if (radius == 0.0) {
[defaults setDouble:kFixedRadius forKey:kPREFERABLE_RADIUS_KEY];
[defaults synchronize];
radius = kFixedRadius;
}
else if (radius == kFixedRadius) {
radius = kFixedRadius;
}
else {
// Emergency mode, set radius to 100 meters
[defaults setDouble:kFixedRadiusEmergency forKey:kPREFERABLE_RADIUS_KEY];
[defaults synchronize];
radius = kFixedRadiusEmergency;
}
return (radius > self.locationManager.maximumRegionMonitoringDistance) ? kFixedRadius : radius;
}
- (void)handleDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.launchOptions = launchOptions;
}
- (BOOL)isMonitoringActivated
{
return self.locationManager.monitoredRegions.allObjects.count > 0;
}
- (void)deactivateMonitoring
{
for (CLCircularRegion *region in self.locationManager.monitoredRegions) {
[self.locationManager stopMonitoringForRegion:region];
}
}
- (CLCircularRegion *)returnGeofenceByUniqueID:(NSString *)geofenceID
{
for (CLCircularRegion *region in self.locationManager.monitoredRegions) {
if ([region.identifier isEqualToString:geofenceID]) {
return region;
}
}
return nil;
}
- (void)deleteGeofenceWithUniqieID:(NSString *)geofenceID
{
for (CLCircularRegion *region in self.locationManager.monitoredRegions) {
if ([region.identifier isEqualToString:geofenceID]) {
[self.locationManager stopMonitoringForRegion:region];
}
}
}
- (void)startMonitoringRegionWithCoordinate:(CLLocationCoordinate2D)coordinate
{
[self startMonitoringRegionWithCoordinate:coordinate andRadius:kPREFERED_RADIUS];
}
- (void)startMonitoringRegionWithCoordinate:(CLLocationCoordinate2D)coordinate andRadius:(CLLocationDirection)radius
{
NSLog(@"%s", __PRETTY_FUNCTION__);
if ([self isMonitoringActivated]) {
[self deactivateMonitoring];
}
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:coordinate
radius:radius
identifier:[self identifierForCoordinate:coordinate]];
if (region) {
CLLocation *lastLocation = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];
[self setLocation:lastLocation];
[self.locationManager startMonitoringForRegion:region];
[self.locationManager requestStateForRegion:region];
}
else NSLog(@"%s, region is nil, %@", __PRETTY_FUNCTION__, region);
}
- (NSArray *)sortedRegionsByDate
{
NSArray *regions = self.locationManager.monitoredRegions.allObjects;
NSArray *regionsNew = [regions sortedArrayUsingComparator:^NSComparisonResult(CLCircularRegion *obj1, CLCircularRegion *obj2) {
NSDate *date1 = (NSDate *)obj1.identifier;
NSDate *date2 = (NSDate *)obj2.identifier;
return [date1 compare:date2];
}];
return regionsNew;
}
- (void)startMonitoringSignificantLocationChanges
{
[self.locationManager startMonitoringSignificantLocationChanges];
}
- (void)stopMonitoringSignificantLocationChanges
{
[self.locationManager stopMonitoringSignificantLocationChanges];
}
- (NSString *)identifierForCoordinate:(CLLocationCoordinate2D)coordinate
{
return [NSString stringWithFormat:@"%@", [NSDate date]];
}
FOUNDATION_EXPORT NSString *NSStringFromCLRegionState(CLRegionState state)
{
if (state == CLRegionStateUnknown) {
return @"CLRegionStateUnknown";
} else if (state == CLRegionStateInside) {
return @"CLRegionStateInside";
} else if (state == CLRegionStateOutside) {
return @"CLRegionStateOutside";
} else {
return [NSString stringWithFormat:@"Undeterminded CLRegionState"];
}
}
- (void)verifyAndSendLastLocation:(CLLocation *)lastLocation
{
NSLog(@"verifyAndSendLastLocation, %@", lastLocation);
CLLocation *oldLocation = [self getLocation];
if (lastLocation && oldLocation) { // New & old location are good
NSTimeInterval seconds = [oldLocation.timestamp timeIntervalSinceDate:lastLocation.timestamp]; // Calculate how seconds passed
NSTimeInterval minutes = fabs(seconds) / 60; // Calculate how minutes passed
BOOL isSameCoordinate =
(oldLocation.coordinate.latitude == lastLocation.coordinate.latitude) &&
(oldLocation.coordinate.longitude == lastLocation.coordinate.longitude) ? YES : NO;
CLLocationDistance distance = [lastLocation distanceFromLocation:oldLocation];
NSLog(@"validateLastLocation, location: %@, minutes: %ld, distance: %f", lastLocation, (long)minutes, distance);
// Distance > 200 or 30 minutes passed or coordinates are different
if (distance >= 200.0 || minutes >= 30.0 || !isSameCoordinate) {
NSLog(@"Distance > 200 or 30 minutes passed or coordinates are different");
[[ServerApiManager sharedInstance] saveLocation:lastLocation]; // Send location to server
}
}
else { // We just starting location updates
NSLog(@"We just starting location updates");
[[ServerApiManager sharedInstance] saveLocation:lastLocation]; // Send new location to server
}
}
- (CLLocation *)getLocation
{
return [self unarchiveLocationForKey:kLAST_LOCATION_KEY];
}
- (CLLocation *)unarchiveLocationForKey:(NSString *)key
{
NSData *unarchivedData = [defaults objectForKey:key];
return (CLLocation *)[NSKeyedUnarchiver unarchiveObjectWithData:unarchivedData];
}
- (BOOL)setLocation:(CLLocation *)lastLocation
{
return [self archiveLocation:lastLocation toDeviceWithKey:kLAST_LOCATION_KEY];
}
- (BOOL)archiveLocation:(CLLocation *)location toDeviceWithKey:(NSString *)key
{
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:location];
[defaults setObject:archivedData forKey:key];
return [defaults synchronize];
}
#pragma mark - CLLocationManagerDelegate Methods
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
{
if (error.domain != kCLErrorDomain || error.code != 5/*) && [manager.monitoredRegions containsObject:region]*/) {
NSLog(@"monitoringDidFailForRegion, error: %@", error.localizedDescription);
NSLog(@"monitoringDidFailForRegion [manager.monitoredRegions containsObject:region]=%@"
,[manager.monitoredRegions containsObject:region]?@"yes":@"No");
NSLog(@"monitoringDidFailForRegion, regions before: %lu", (unsigned long)manager.monitoredRegions.allObjects.count);
}
if (manager.monitoredRegions.allObjects.count >= 19) {
[self startMonitoringRegionWithCoordinate:manager.location.coordinate];
}
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
if (![CLLocationManager locationServicesEnabled] || status == kCLAuthorizationStatusDenied || status == kCLAuthorizationStatusRestricted) {
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) return;
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:MFLocalizedString(@"mobile_control_team", nil)
message:MFLocalizedString(@"gps_and_wifi_text", nil)
delegate:self
cancelButtonTitle:MFLocalizedString(@"ok", nil)
otherButtonTitles:nil];
[alertView show];
});
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
NSLog(@"didUpdateLocations: %@", locations);
CLLocation *lastLocation = (CLLocation *)locations.lastObject;
CLLocationCoordinate2D coordinate = lastLocation.coordinate;
if (lastLocation == nil || coordinate.latitude == 0.0 || coordinate.longitude == 0.0) return;
NSLog(@"didUpdateLocations, save location from didUpdateLocations.");
[self startMonitoringRegionWithCoordinate:coordinate];
[self verifyAndSendLastLocation:lastLocation];
}
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
NSLog(@"didStartMonitoringForRegion: %@", region);
[manager requestStateForRegion:region];
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSLog(@"didDetermineState, currentLocation: {%f, %f}, regionState: %@, region: %@",
manager.location.coordinate.latitude, manager.location.coordinate.longitude, NSStringFromCLRegionState(state), region);
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(@"didEnterRegion: %@", region);
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSTimeInterval minutes = ([UIApplication sharedApplication].backgroundTimeRemaining/60);
NSLog(@"didExitRegion, applicationState: %@, backgroundTimeRemaining(minutes): %f",
NSStringFromUIApplicationState([UIApplication sharedApplication].applicationState), minutes);
CLLocation *lastLocation = manager.location;
if (lastLocation == nil || lastLocation.coordinate.latitude == 0.0 || lastLocation.coordinate.longitude == 0.0) return;
NSLog(@"didExitRegion, save location from geofence.");
[self startMonitoringRegionWithCoordinate:lastLocation.coordinate];
[self verifyAndSendLastLocation:lastLocation];
}
Eventually I tried a new project and tested it:
Geofence.h
Geofence.m
And log in iPhone 4, iOS 7.1.2, app is not running:
Problem is: Assuming user switched off device and switched on again in a different region, the device won't monitor current location and for that I assume I need to use
significantLocationChanges
.But I'm affraid using using
significantLocationChanges
will screw up something.