Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

didEnterRegion works in foreground but not background or other VCs

If the app is running and the CLLocationManagerDelegate class is the foreground (i.e. visible) then the didEnterRegions triggers and I get both the NSLog as well as the AlertView. However, I get nothing when the app is in the background or, essentially, if the screen is showing anything but the delegate class.

I have set "App registers for location updates" under "Required background modes" in the plist although I'm not sure that's even necessary.

Here's what I think is the relevant code although I may be wrong (and will gladly add more). I should note that everything in viewDidLoad is wrapped in an if which checks if region monitoring is available and enabled.

- (void)viewDidLoad
{
    NSLog(@"MapViewController - viewDidLoad");
    self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
    self.locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters;    
    self.locationManager.delegate = self;
    [self.locationManager startMonitoringSignificantLocationChanges];
}

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
    NSLog(@"MapViewController - didEnterRegion");
    NSLog(@"MVC - didEnterRegion - region.radius = %f", region.radius);
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"entered region..." message:@"You have Entered the Location." delegate:nil cancelButtonTitle:@"OK"  otherButtonTitles: nil];
    alert.tag = 2;
    [alert show];
}

here is where I get the list of regions being monitored, in AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

// other code

NSLog(@"LISTING ALL REGIONS MONITORED");
    NSArray *regions = [self.locationManager.monitoredRegions allObjects];
    if (!regions) {
        NSLog(@"no regions found");
    } else {
        NSLog(@"got %d monitored regions", [regions count]);
        for (int i = 0; i < [regions count]; i++) {
            CLRegion *region = [regions objectAtIndex:i];
            NSLog(@"region %d's identifier = %@", i, region.identifier);
            NSLog(@"region: radius: %@", region.radius);
        }
    }

// other code
}

I call startMonitoringForRegion twice, here's the main place:

- (void)doneButtonTapped {
    NSLog(@"doneButtonTapped");

    if (self.locationIdentifier) {
        if ([CLLocationManager regionMonitoringEnabled] && [CLLocationManager regionMonitoringAvailable]) {

            // core data setup
            NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
            NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"LocationReminder" inManagedObjectContext:self.managedObjectContext];
            fetchRequest.entity = entityDescription;
            NSPredicate *predicate = [NSPredicate predicateWithFormat:@"locationIdentifier == %@", self.locationIdentifier];
            fetchRequest.predicate = predicate;
            NSError *error;
            NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
            if (results) {

                // get the LocationReminder
                LocationReminder *retrievedReminder = [results objectAtIndex:0];
                retrievedReminder.audioURI = [[[self.audioPlayers objectAtIndex:self.selectedCell] url] absoluteString];
                retrievedReminder.userRecording = nil;

                // start monitoring it's region
                NSArray *coordinateArray = [retrievedReminder.locationIdentifier componentsSeparatedByString:@", "];
                CLLocationCoordinate2D coordinate = {[[coordinateArray objectAtIndex:0] doubleValue], [[coordinateArray objectAtIndex:1] doubleValue]};
                CLRegion *newRegion = [[CLRegion alloc] initCircularRegionWithCenter:coordinate radius:250.0 identifier:retrievedReminder.locationIdentifier];
                NSLog(@"about to monitor region with radius: %f", newRegion.radius);
                [self.locationManager startMonitoringForRegion:newRegion desiredAccuracy:kCLLocationAccuracyBest];

                // save the LocationReminder
                if (![self.managedObjectContext save:&error]) {
                    NSLog(@"hmm.  no managed object context.  must be something space-time going on");
                } else {
                    NSLog(@"saved locationReminder, locationIdentifier = %@", retrievedReminder.locationIdentifier);
                }
            } else {
                NSLog(@"ERROR: no LocationReminder retreived for predicate: %@", predicate);
            }
        }

        // get the mapview controller off of the navigation stack
        for (UIViewController *viewController in self.navigationController.viewControllers) {
            if ([viewController isKindOfClass:[MapViewController class]]) { 
                MapViewController *mapVC = (MapViewController *)viewController;
                mapVC.audioURI = [[[self.audioPlayers objectAtIndex:self.selectedCell] url] absoluteString];
                [self.navigationController popToViewController:mapVC animated:YES];
            }
        }
}

And because I get the feeling that it might be important, here's the getter for locationManager:

- (CLLocationManager *)locationManager {
    NSLog(@"MapViewController - locationManager");
    if (_locationManager) {
        return _locationManager;
    } else {
        _locationManager = [[CLLocationManager alloc] init];
        return _locationManager;
    }
}

UPDATE 1: Via the Apple forums (where I crossposted) someone mentioned that AlertView will only show in the foreground. Still the NSLog doesn't fire either. I'm assuming that should work.

like image 266
ari gold Avatar asked Sep 10 '12 22:09

ari gold


2 Answers

A friend of mine wrote up a nice tutorial on using geofencing that might help clear up some issues you are having.

Get started with geofencing

There are plenty of examples online and here on SO. Start out small and work your way up. Once you start getting your callbacks, you can start expanding things out to your other view controllers.

UPDATE

As explained in the comments the benefits of creating a singleton class to control your location manager and delegate methods. By using a singleton, you prevent the possibility of getting multiple calls to your delegate methods. You can prevent this by careful coding, but using a singleton does this for you. This is also a nice class to handle all the work needing to be done by your delegate methods.

like image 60
Bill Burgess Avatar answered Oct 13 '22 20:10

Bill Burgess


Things you are doing wrong:

  1. Background modes - App registers for location updates. This is not needed. This is need when you want to gather info for significant changes in location etc. So, go to Targets > Your app > Capabilites , and select the desired option under Background modes. This will automatically update the plist for you. For now, disable it.
  2. You are trying to create an alert when the user enters a region. While this while work when app is working, an alert is of no use when your app is in background. Do - Rather trigger a local notification or an api call.

eg. of a notification:

    -(void)triggerLocalNotification:(CLRegion *)region{
    UILocalNotification *notification = [[UILocalNotification alloc]init];
    [notification setAlertBody:[NSString stringWithFormat:@"Welcome to %@", [region identifier]]];
    [notification setRepeatInterval:0];
    [notification setFireDate:[NSDate dateWithTimeIntervalSinceNow:2]];
    [notification setTimeZone:[NSTimeZone  defaultTimeZone]];
    [[UIApplication sharedApplication]scheduleLocalNotification:notification];
    NSLog(@"notification triggered with notification %@", notification);
}
like image 31
Gautam Jain Avatar answered Oct 13 '22 20:10

Gautam Jain