iOS Geofencing API Tutorial Using Core Location Framework

In iOS Geofencing API tutorial, I have explained how to monitor Geo-fences (Regions) using CoreLocation.framework in iOS.

Major updates in Corelocation.Framework after iOS 7

  • regionMonitoringAvailable() deprecated.
  • isMonitoringAvailableForClass() is added in CLLocationManager.
  • requestStateForRegion() is added in CLLocationManager.
  • initCircularRegionWithCenter() deprecated in CLRegion.
  • CLCircularRegion class is added , replacement for CLRegion.

 

Download iOS Geofencing Tutorial

What is Geofence.

A Geofence is an area defined by circle of a specified radius around a known point on the Earth’s surface. In Objective-C, a Geofence is represented by CLRegion/CLCircularRegionCLCircularRegion is replacement for CLRegion introduced in iOS 7.
You can use the below code to create a Geofence from latitude,longitude and radius. Each fence you create must have unique identifier to identify region later.

- (CLRegion*)dictToRegion:(NSDictionary*)dictionary
{
    NSString *identifier = [dictionary valueForKey:@"identifier"];
    CLLocationDegrees latitude = [[dictionary valueForKey:@"latitude"] doubleValue];
    CLLocationDegrees longitude =[[dictionary valueForKey:@"longitude"] doubleValue];
    CLLocationCoordinate2D centerCoordinate = CLLocationCoordinate2DMake(latitude, longitude);
    CLLocationDistance regionRadius = [[dictionary valueForKey:@"radius"] doubleValue];

    if(regionRadius > locationManager.maximumRegionMonitoringDistance)
    {
        regionRadius = locationManager.maximumRegionMonitoringDistance;
    }

    NSString *version = [[UIDevice currentDevice] systemVersion];
    CLRegion * region =nil;

    if([version floatValue] >= 7.0f) //for iOS7
    {
        region =  [[CLCircularRegion alloc] initWithCenter:centerCoordinate
                                                   radius:regionRadius
                                               identifier:identifier];
    }
    else // iOS 7 below
    {
        region = [[CLRegion alloc] initCircularRegionWithCenter:centerCoordinate
                                                       radius:regionRadius
                                                   identifier:identifier];
    }
    return  region;
}

 

Check the Availability of  iOS Geofencing API

To check whether Location services enabled and Region monitoring is enabled, you can use the following API.
Note: regionMonitoringAvailable is deprecated in iOS 7.

if(![CLLocationManager locationServicesEnabled])
{
    //You need to enable Location Services
}
if(![CLLocationManager isMonitoringAvailableForClass:[CLRegion class]])
{
    //Region monitoring is not available for this Class;
}
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied ||
   [CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted  )
{
   //You need to authorize Location Services for the APP
}

Initializing CLLocationManager

iOS8 updates:

In iOS8, we need to add a key NSLocationAlwaysUsageDescription in the Info.plist. Otherwise Geofencing will fail silently.You can enter any string like “Location is required for geofence”.

	<key>NSLocationAlwaysUsageDescription</key>
	<string>Location is required for geofence</string>

And you need to call [locationManager requestAlwaysAuthorization]

 

You can use the below code to initialize Core Location manager.

CLLocationManager *locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager requestAlwaysAuthorization];

To get Boundary-crossing events, you need to implement CLLocationManagerDelegate methods.

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

}

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

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

}

Getting Initial Geofence

iOS Geofencing API provides only boundary crossing events. If a user is inside a region at the time of registration, the location manager does not generate any event. Instead, your app must wait for the user to cross the region boundary before an event is generated and sent to the delegate.
In iOS7, we can use requestStateForRegion: method of the CLLocationManager class to check whether the user is already inside a fence.
You need to implement below delegate method to receive the event.

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

}

But iOS 6 & 5, don’t have any API to get the initial Geofence. So we need to calculate the device distance from the Geofences and check whether it falls inside any geofence. You need to follow the steps to get initial fence.
1) Start location manager to get the initial location.

[locationManager startUpdatingLocation];

 

2) Implement the below delegate method. Whenever you get first location update, check whether device is inside any fence. If the device is inside any geofence, manually invoke [locationManager: didEnterRegion:] . After getting the initial fence, we can stop the location updates.

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
    static BOOL firstTime=TRUE;
    if(firstTime)
    {
        firstTime = FALSE;
        NSSet * monitoredRegions = locationManager.monitoredRegions;
        if(monitoredRegions)
        {
            [monitoredRegions enumerateObjectsUsingBlock:^(CLRegion *region,BOOL *stop)
             {
                 NSString *identifer = region.identifier;
                 CLLocationCoordinate2D centerCoords =region.center;
                 CLLocationCoordinate2D currentCoords= CLLocationCoordinate2DMake(newLocation.coordinate.latitude,newLocation.coordinate.longitude);
                 CLLocationDistance radius = region.radius;

                 NSNumber * currentLocationDistance =[self calculateDistanceInMetersBetweenCoord:currentCoords coord:centerCoords];
                 if([currentLocationDistance floatValue] < radius)
                 {
                         NSLog(@"Invoking didEnterRegion Manually for region: %@",identifer);

                        //stop Monitoring Region temporarily
                         [locationManager stopMonitoringForRegion:region];

                         [self locationManager:locationManager didEnterRegion:region];
                         //start Monitoing Region again.
                         [locationManager startMonitoringForRegion:region];
                 }
             }];
        }
        //Stop Location Updation, we dont need it now.
        [locationManager stopUpdatingLocation];

    }
}

You can use the below API to calculate distance between two co-ordinates.

- (NSNumber*)calculateDistanceInMetersBetweenCoord:(CLLocationCoordinate2D)coord1 coord:(CLLocationCoordinate2D)coord2 {
    NSInteger nRadius = 6371; // Earth's radius in Kilometers
    double latDiff = (coord2.latitude - coord1.latitude) * (M_PI/180);
    double lonDiff = (coord2.longitude - coord1.longitude) * (M_PI/180);
    double lat1InRadians = coord1.latitude * (M_PI/180);
    double lat2InRadians = coord2.latitude * (M_PI/180);
    double nA = pow ( sin(latDiff/2), 2 ) + cos(lat1InRadians) * cos(lat2InRadians) * pow ( sin(lonDiff/2), 2 );
    double nC = 2 * atan2( sqrt(nA), sqrt( 1 - nA ));
    double nD = nRadius * nC;
    // convert to meters
    return @(nD*1000);
}

 

How to Simulate Geofence in Device

1). You can add GPX files to your project and change the location to see the functionality.
Below is sample GPX File.

<?xml version="1.0"?>
<gpx version="1.1" creator="Xcode">
    <wpt lat="41.88535" lon="-87.62745">
         <name>Fence 1</name>
	</wpt>
</gpx>

iOS Geofencing API Simulation

2). Go to menu “Product” => “Scheme” => “Edit Scheme” and make sure you have checked “Allow Location Simulation” as shown in the below image.
Add GPX files

 

Reference: Apple Documentation