0

I am monitoring both iBeacons and circular regions in iOS. If I try to define them separately:

- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLCircularRegion *)region {}
- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLBeaconRegion *)region {}

It is obviously a duplicate declaration error. This SO answer nicely indicates, how to differentiate which region fired. To get that far, I'm trying to figure out how to define a common region exit event, which everyone would be happy with. If I combine them using CLRegion, there will be various warnings:

- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
    if (region.class == CLCircularRegion.class) {
        CLLocationCoordinate2D coordinate = [region center];  // Deprecation warning, use CLCircularRegion instead
        // etc...
    }
    if (region.class == CLBeaconRegion.class) {
        [locationManager stopRangingBeaconsInRegion:region]; // Incompatible pointer types warning
        // etc...
    }
}

I originally defined beacon processing using didDetermineState, but it results in some extra events. E.g. CLRegionStateOutside fires initially for all monitored regions, which is entirely appropriate, but I don't need to know that, so I would rather be using didExitRegion instead.

Is there a proper way to write a clean combined didExitRegion so that it can process both circular and beacon regions without errors or warnings?

MiRin
  • 93
  • 9

1 Answers1

3

The method signature is defined by the CLLocationManagerDelegate protocol and is:

- (void)locationManager:(CLLocationManager *)manager 
     didExitRegion:(CLRegion *)region;

You must use this signature if you want the method to be called.

You are on the right track by checking for the type of region that you were passed. The bit you are missing is to use a cast to assign region to a variable of the appropriate subclass so that you don't get warnings/errors when you try and do sub-class specific things.

Note that it is also safer to use isKindOfClass: rather than comparing the classes directly. isKindOfClass returns YES if the object is an instance of the specified class or a subclass of the specified class. So, if Apple makes some internal change and starts sending you CLNewBeaconRegion objects which is a subclass of CLBeaconRegion your code will still work.

- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
    if ([region isKindOfClass:CLCircularRegion.class]) {
        CLCircularRegion *circularRegion = (CLCircularRegion *)region;
        CLLocationCoordinate2D coordinate = circularRegion.center;
        // etc...
    } else if ([region isKindOfClass:CLBeaconRegion.class]) {
        CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region;
        [locationManager stopRangingBeaconsInRegion:beaconRegion];
        // etc...
    }
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • Thank you, works perfectly! I thought casting would be the way, but got confused after reading [this](https://stackoverflow.com/a/1236555/5528498): "You can't just cast a super class to its subclass. It doesn't actually implement any of your added variables/properties or methods." I needed some confirmation that this is indeed an appropriate way to go. – MiRin Apr 08 '18 at 10:10
  • 1
    You can't cast an instance of a superclass to a subclass, but in this case you have an instance of the subclass (which you have verified through `isKindOfClass`) so you can perform the cast. – Paulw11 Apr 08 '18 at 10:13