Geofencing with Core Location: Getting Started

In this geofencing tutorial, you’ll learn how to create and use geofences in iOS with Swift using the Core Location framework. By Michael Katz.

4.4 (5) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Registering the Geofences

With the location manager configured, you must now allow your app to register user geofences for monitoring.

How Geofencing Works

Geofencing, or region monitoring as it’s known in Apple parlance, requires setting up a circle to monitor. This is defined as a CLCircularRegion, with a center coordinate and a radius (in meters).

The device can listen for the user entering and/or exiting a circular fence region. An entrance event will fire when the user moves from outside the circle to inside the circle.

A notification is fired when user enters a region

An exit event fires when the user leaves the circle.

A notification is fired when user exits a region

The location manager callback is called if the following conditions are satisfied:

  • The device is capable of monitoring location.
  • Monitoring conditions are good enough to resolve a region — a magical combination of GPS, Wi-Fi, sufficient battery and other hardware considerations.
  • The user has enabled location services.
  • The user has granted Always location permissions with precise monitoring.
  • The app isn’t monitoring more than 20 regions.

The app stores the user geofence information within your custom Geotification model. But to monitor geofences, Core Location requires you to represent each one as a CLCircularRegion instance. To handle this, you’ll create a helper method that returns a CLCircularRegion from a given Geotification object.

Creating Regions

Open Geotification.swift and add the following method to the empty extension at the bottom of the file:

var region: CLCircularRegion {
  // 1
  let region = CLCircularRegion(
    center: coordinate,
    radius: radius,
    identifier: identifier)

  // 2
  region.notifyOnEntry = (eventType == .onEntry)
  region.notifyOnExit = !region.notifyOnEntry
  return region
}

Here’s what the code above does:

  1. It initializes a CLCircularRegion with the location of the geofence, the radius of the geofence and an identifier that allows iOS to distinguish between the registered geofences of a given app. The initialization is straightforward, as the model already contains the required properties.
  2. CLCircularRegion also has two Boolean properties: notifyOnEntry and notifyOnExit. These flags specify whether to trigger geofence events when the device enters or leaves the defined geofence, respectively. Since you’re designing your app to allow only one notification type per geofence, set one of the flags to true and the other to false based on the eventType value stored in the geotification object.

Next, you need a method to start monitoring a given geotification whenever the user adds one.

Monitoring Regions

Open GeotificationsViewController.swift and add the following method to the body of GeotificationsViewController:

func startMonitoring(geotification: Geotification) {
  // 1
  if !CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
    showAlert(
      withTitle: "Error",
      message: "Geofencing is not supported on this device!")
    return
  }

  // 2
  let fenceRegion = geotification.region
  // 3
  locationManager.startMonitoring(for: fenceRegion)
}

Here’s an overview of this method:

  1. isMonitoringAvailableForClass(_:) determines if the device has the required hardware to support the monitoring of geofences. If monitoring is unavailable, bail out and alert the user.
  2. Create a CLCircularRegion instance from the given geotification using the helper method you defined earlier.
  3. Register the CLCircularRegion instance with Core Location for monitoring via CLLocationManager.
Note: iOS triggers a geofence event if it detects a boundary crossing. If the user is already within a geofence at the point of registration, iOS won’t generate an event. If you need to query if the device location falls inside or outside a given geofence, CLLocationManager has a method called requestStateForRegion(_:).

With your start method done, you also need a method to stop monitoring a given geotification when the user removes it from the app.

In GeotificationsViewController.swift, add the following method below startMonitoring(geotificiation:):

func stopMonitoring(geotification: Geotification) {
  for region in locationManager.monitoredRegions {
    guard 
      let circularRegion = region as? CLCircularRegion, 
      circularRegion.identifier == geotification.identifier 
    else { continue }

    locationManager.stopMonitoring(for: circularRegion)
  }
}

The method instructs locationManager to stop monitoring the CLCircularRegion associated with the given geotification.

Adding Geotifications

Now that both the start and stop methods are complete, you’ll use them whenever you add or remove a geotification. You’ll begin with the adding part.

First, take a look at addGeotificationViewController(_:didAddGeotification:) in GeotificationsViewController.swift.

This is the delegate method invoked by AddGeotificationViewController upon creating a geotification. It’s responsible for creating a new Geotification object and updating both the map view and the geotifications list accordingly. It calls saveAllGeotifications(), which takes the updated geotifications list and persists it via UserDefaults.

Now, replace addGeotificationViewController(_:didAddGeotification:) with the following:


func addGeotificationViewController(
  _ controller: AddGeotificationViewController,
  didAddGeotification geotification: Geotification
) {
  controller.dismiss(animated: true, completion: nil)

  // 1
  geotification.clampRadius(maxRadius:
    locationManager.maximumRegionMonitoringDistance)
  add(geotification)

  // 2
  startMonitoring(geotification: geotification)
  saveAllGeotifications()
}

You’ve made two key changes to the code:

  1. You ensure the value of the radius doesn’t exceed the maximumRegionMonitoringDistance property of locationManager, which defines the largest radius, in meters, for a geofence. This is important, as any value that exceeds this maximum will cause monitoring to fail.
  2. You call startMonitoring(geotification:) to register the newly added geotification with Core Location for monitoring.

At this point, the app is capable of registering new geofences for monitoring. But there’s a limitation: As geofences are a shared system resource, Core Location restricts the number of registered geofences to a maximum of 20 per app.

While there are workarounds, for this tutorial, you’ll take the approach of limiting the number of geotifications the user can add.

Add the following to the end of updateGeotificationsCount():

navigationItem.rightBarButtonItem?.isEnabled = (geotifications.count < 20)

This line disables the Add button in the navigation bar whenever the app reaches the limit.

Removing Geotifications

Now you need to deal with the removal of geotifications. You'll handle this functionality in mapView(_:annotationView:calloutAccessoryControlTapped:), which is invoked whenever the user taps the "delete" accessory control on an annotation.

In mapView(_:annotationView:calloutAccessoryControlTapped:), before remove(geotification), add the following:

stopMonitoring(geotification: geotification)

This stops monitoring the geofence associated with the geotification before removing it and saving the changes to UserDefaults.

At this point, your app is capable of monitoring and un-monitoring user geofences. Hurray!

Build and run. You won't see any changes, but the app will now be able to register geofence regions for monitoring. However, it won't yet be able to react to any geofence events. Not to worry — this is the next order of business!