9

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.

ari gold
  • 2,074
  • 3
  • 25
  • 51
  • Can you include the code where you are calling the startMonitoringForRegion? The included code right now doesn't show that and would only trigger callback methods related to significant location changes. And those event won't fire from the simulator in my experience. – Bill Burgess Sep 12 '12 at 13:03

4 Answers4

2

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.

Community
  • 1
  • 1
Bill Burgess
  • 14,054
  • 6
  • 49
  • 86
  • 2
    Thanks for the link. I'm pretty sure that I've got all those bases covered. At this point I think it's more about how apps go into and out of the background than region monitoring. In fact, it makes a fair amount of sense to me that it only works in the CLLocationManagerDelegate - how is didEnterRegion supposed to be called? Does iOS know enough to wake up the app AND instantiate the proper VC? I'm wondering if it has to do something with the UIApplicationDelegate protocols. – ari gold Sep 12 '12 at 20:26
  • 1
    On the other hand, UIApplicationDelegate isn't mentioned at all in the Location Awareness Programming Guide which leads me to think that it's not important. Still confused as to how the OS is supposed to know where didEnterRegion is implemented and how to access that class if the app is in the background... – ari gold Sep 12 '12 at 20:28
  • Pretty sure it goes based on whatever class is the CLLocationManagerDelegate. That class will be notified of any callbacks. That is how I have mine set up. I also created a singleton for my location manager class to avoid getting multiple callbacks. – Bill Burgess Sep 12 '12 at 20:37
  • I think I've got it: I think it was indeed the fact that since I didn't use a singleton, I had several location managers. Now that I have a singleton I think things are firing but I want to be sure. – ari gold Sep 12 '12 at 23:58
  • That helped me a lot. I used to get several events firing at once. The singleton helped sort things out. Glad things are working better. – Bill Burgess Sep 13 '12 at 13:29
  • If you'd like to write up the benefits of a singleton I can accept your answer. If you'd rather not I can also write it up. Either way, you're the tops. – ari gold Sep 13 '12 at 15:46
  • So it works in the background but it doesn't work with the screen off. I'm going to keep looking on SO but any ideas? Seems strange that screen off ≠ background. – ari gold Sep 14 '12 at 22:17
  • 1
    Screen off or going to the background, you should make sure to set your location manager delegate, in case it isn't set. I set the delegate to my locationController in the -applicationDidEnterBackground and that should cover background and screen off events. – Bill Burgess Sep 15 '12 at 01:52
  • Say, do you use **standard** or **significant change** location services? Or maybe a combo of both? Also, bringing up how AppDelegate's UIApplicationDelegate's protocols interact with Core Location is great - I think Apple should include it as a chapter in the Location Awareness guide. – ari gold Sep 15 '12 at 18:36
  • 1
    For region monitoring I don't use either. Once you start monitoring a region, you should be good to go. That is one of the benefits of region monitoring. You don't have to register for background location either. – Bill Burgess Sep 16 '12 at 11:51
  • Hmm. That's what I figured as it is listed as one of the three location services choices but then the sample app (Regions) uses both. Though maybe that's for updating the user's location. – ari gold Sep 16 '12 at 18:53
  • Do you notice any change in responsiveness or accuracy if the app is in the foreground as compared to the background/screen off? I've read in your other posts how you find region monitoring to be very accurate but I'm finding less background/screen off triggering..it seems like it uses the GPS in the foreground but cell towers and Wi-Fi in the background/screen off... – ari gold Sep 16 '12 at 18:58
  • Should be the same either way. But I haven't noticed one method more accurate than another. You can specify accuracy on your locationManager. I use Best accuracy for it. No significant hits to battery at all. Also depends on the area. Metro/High Wifi areas are much better than rural, low wifi areas. It definitely changes depending on the area. – Bill Burgess Sep 17 '12 at 12:53
  • Hmm. I wonder if part of the problem I'm having is that I created regions using one CLLocationManager and then, as per your suggestions, moved the CLLocationManager to a singleton effectively destroying the CLLocationManager that created said regions. I'm going to wipe the app and make some fresh regions from a fresh CLLocationManager and see what happens. – ari gold Sep 19 '12 at 03:50
  • 1
    current link: http://www.creativebloq.com/ipad/get-started-geofencing-ios-9122867 – MrTJ Apr 10 '14 at 14:23
2

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);
}
Gautam Jain
  • 2,913
  • 30
  • 25
  • startMonitoringSignificantLocationChanges and startMonitoringForRegion is work in background mode? – Raees Madathil Sep 09 '15 at 05:55
  • If you are doing startMonitoringSignificantLocationChanges, then yes you need background modes. You need your app to wake up when location changes significantly and you need to tell that to your system. But you don't need it for startMonitoringForRegion. startMonitoringForRegion will call the didEnterRegion and didExitRegion regardless of background modes. – Gautam Jain Sep 09 '15 at 09:34
  • Thanks a lot, I want to use CLLocationManager with minimum battery consumption. – Raees Madathil Sep 09 '15 at 09:43
0

You can post a local notification when you didEnterRegion.

This will show an alert-like popup even if you're in the background.

You can do a simple test:

1) Create a Local notification object inside your applicationDidEnterBackground of your app delegate, with any random message and tell the local notification to fire immediately.

2) Press the home button, when you app minimise, you should see a popup.

Zhang
  • 11,549
  • 7
  • 57
  • 87
  • Thanks for the suggestions - I wasn't familiar with the UIApplicationDelegate protocols and now I'm wondering if that's not where the issue is. Take a look below at my comments to Bill Burgess. – ari gold Sep 12 '12 at 20:28
0

i think you need go to your app.plist

and add Required Background modes : add itme App registers for location update

and 1 . if you app is in background , you still see the arrow on top

and 2 , if the app killed , you can still see a hollow arrow on the top , ios will monitor the region for you , but limited to 20 regions

chings228
  • 1,859
  • 24
  • 24