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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Geofencing with Core Location: Getting Started
30 mins
- Getting Started
- Do You Know the Way to San Jose?
- Say Hi to Tim
- Setting Up Core Location
- Location Permissions
- Showing the User’s Location
- Registering the Geofences
- How Geofencing Works
- Creating Regions
- Monitoring Regions
- Adding Geotifications
- Removing Geotifications
- Reacting to Geofence Events
- Handling Location Errors
- Handling Location Events
- Comings and Goings
- Simulating Location Events
- Going on an Imaginary Drive
- Notifying the User of Geofence Events
- Building the Notification
- Where to Go From Here?
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.
An exit event fires when the user leaves the circle.
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:
- 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. -
CLCircularRegion
also has two Boolean properties:notifyOnEntry
andnotifyOnExit
. 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 totrue
and the other tofalse
based on theeventType
value stored in thegeotification
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:
-
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. - Create a
CLCircularRegion
instance from the given geotification using the helper method you defined earlier. - Register the
CLCircularRegion
instance with Core Location for monitoring viaCLLocationManager
.
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:
- You ensure the value of the radius doesn’t exceed the
maximumRegionMonitoringDistance
property oflocationManager
, 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. - 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!