10

I have been unable to work out how to handle a scenario where the phone is already inside a region when startMonitoringForRegion is called? Other questions have suggested calling requestStateForRegion inside didStartMonitoringForRegion this then calls the method didDetermineState: forRegion:. So the code looks something like this:

- (void)viewDidLoad {
    //location manager set up etc...
    for (Object *object in allObjects){

        CLRegion *region = [self geofenceRegion:object];
        [locationManager startMonitoringForRegion:region];
     }
}

- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {

    [self.locationManager requestStateForRegion:region];
    [self.locationManager performSelector:@selector(requestStateForRegion:) withObject:region afterDelay:5];
 }

- (void)locationManager:(CLLocationManager *)manager
  didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {

    if (state == CLRegionStateInside){
        [self locationManager:locationManager didEnterRegion:region];
    }  
}

Now obviously the method geofenceRegion is my own and it works fine, and the objects contains things like lat long and radius and that all works fine as well so that is not the problem here.

Anyway, the problem with the above code is that it does work if the user is already inside the region when it adds the region to their device (ie. didEnterRegion is done). However the problem is that the method didDetermineState: forRegion: is also called every time one of the boundary regions is crossed as per the apple docs:

The location manager calls this method whenever there is a boundary transition for a region. It calls this method in addition to calling the locationManager:didEnterRegion: and locationManager:didExitRegion: methods. The location manager also calls this method in response to a call to its requestStateForRegion: method, which runs asynchronously.

Now because of this every time a region is entered, didEnterRegion is automatically called but then it is called again because didDetermineState: forRegion: is also automatically called as per the apple docs and this results in didEnterRegion being called again so the region is entered twice when i only want it to be entered once. How can i avoid this?

Thanks for your help.

SOLUTION

The solution really is so simple i was just going about it the wrong way. I had to choose to either use the 2 methods didEnterRegion: and didExitRegion or use didDetermineState: forRegion and create my own methods for entering and exiting the region, both should not be used.

So i have chosen to use only the didDetermineState: forRegion method and my code now looks like this:

Please note that with this method exit region will be called for the region if not inside and if, like me, you only want exit to happen after an enter has happened you will need some sort of method of checking if the region has already been entered (I myself used core data as i was already using this to store other aspects of the regions).

- (void)viewDidLoad {
    //location manager set up etc...
    for (Object *object in allObjects){

        CLRegion *region = [self geofenceRegion:object];
        [locationManager startMonitoringForRegion:region];
     }
}

- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {

    [self.locationManager performSelector:@selector(requestStateForRegion:) withObject:region afterDelay:5];
}

- (void)locationManager:(CLLocationManager *)manager
  didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {

    if (state == CLRegionStateInside){

        [self enterGeofence:region];

    } else if (state == CLRegionStateOutside){

        [self exitGeofence:region];

    } else if (state == CLRegionStateUnknown){
        NSLog(@"Unknown state for geofence: %@", region);
        return;
    }
}

- (void)enterGeofence:(CLRegion *)geofence {

    //whatever is required when entered
}

- (void)exitGeofence:(CLRegion *)geofence {

    //whatever is required when exit
}
Joe Maher
  • 5,354
  • 5
  • 28
  • 44
  • Subject to this [meta post](https://meta.stackoverflow.com/q/262806/5175709). It's better to not include the answer in the question itself. Either write a separate answer or just accept a given answer or write a comment. – mfaani Jul 28 '17 at 12:23

1 Answers1

6

Just do not use locationManager:didEnterRegion: at all, as locationManager:didDetermineState:forRegion: gives you all the info you need to trigger the on-entry code, which, by the way, should not be the locationManager:didEnterRegion:, use your own selector, which is not a part of CLLocationManagerDelegate protocol.

Another approach is to test for inside a region location when starting to monitor a region. This solution is not that trivial as it sounds though: you need to update current location first by calling startUpdatingLocation, as just reading location property of locationManager will probably give you stale or extremely inaccurate reading.

Alex Pavlov
  • 991
  • 6
  • 9
  • Whats the point of the API if i have to track it myself though? that just doesn't seem logical at all? – Joe Maher May 17 '15 at 02:18
  • The API description clearly states that it monitors entry and exit events. You want to get more than API does, namely firing entry event when starting monitoring from inside a region. There is a lower level API callback for that, the locationManager:didDetermineState:forRegion: and you may use it, but trying to combine it with higher level stuff is asking for a trouble. – Alex Pavlov May 17 '15 at 02:23
  • But as i mentioned in my question didDetermineState is also triggered when a region is entered or exit. So what your are saying is i have to manually get the current location when i start monitoring a region, and if this current location is within the region i handle it myself. Ignoring the requestStateForRegion?. I spose i just can't think of what else you would use requestStateForRegion for if not using region monitoring – Joe Maher May 17 '15 at 02:33
  • The requestStateForRegion does exactly what it says on the tin - it requests a state for a region. Without that method you would have to start standard location updates, keep processing these updates until getting a reading with desired accuracy and stop the updates after that, then you would have to check if the location is inside of the region circle. – Alex Pavlov May 17 '15 at 03:08
  • But why would you request a state of a region if you were not already monitoring it? – Joe Maher May 17 '15 at 03:13
  • Because you want to know if you are already inside of the region when you just started to monitor it. Like I said, the alternate method would be to go extra steps to determine a location. – Alex Pavlov May 17 '15 at 03:18
  • Thats exactly what i want to do, but like i said, when monitoring the region it is going to call didDetermineStateForRegion every single time it crosses the boundary of a region. So if inside this function you are telling it to enter the region it will enter the region twice. Sorry i feel like i'm not explaining this properly – Joe Maher May 17 '15 at 03:20
  • Joe, what I suggested is you use just one method: either didDetermineStateForRegion or didEnterRegion/didExitRegion, but not both. By the way, you do not need two distinct "unknown" states, as you are want to trigger entry code whenever you get inside state. I will update the answer in a moment. Probably this was the source of confusion. – Alex Pavlov May 17 '15 at 03:25
  • Thankyou Alex for all your help, i've updated my answer with how i implemented your solution – Joe Maher May 17 '15 at 08:28