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?
Reacting to Geofence Events
When the user enters or leaves a geofence region, your app could be running in the foreground, running in the background, or not running at all! You have to deal with all of these possibilities.
Handling Location Errors
You'll start by implementing some of the delegate methods to facilitate error handling. These are important to add in case anything goes wrong.
In GeotificationsViewController.swift, add the following methods to CLLocationManagerDelegate
:
func locationManager(
_ manager: CLLocationManager,
monitoringDidFailFor region: CLRegion?,
withError error: Error
) {
guard let region = region else {
print("Monitoring failed for unknown region")
return
}
print("Monitoring failed for region with identifier: \(region.identifier)")
}
func locationManager(
_ manager: CLLocationManager,
didFailWithError error: Error
) {
print("Location Manager failed with the following error: \(error)")
}
These delegate methods log any errors the location manager encounters to facilitate your debugging.
Handling Location Events
Next, you'll add the code to listen for and react to geofence entry and exit events.
Open SceneDelegate.swift and add the following line at the top of the file to import the CoreLocation
framework:
import CoreLocation
Add a new property below var window: UIWindow?
:
let locationManager = CLLocationManager()
Then add the following method. It's called when the scene is activated:
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
}
You've set up your SceneDelegate
to receive geofence-related events. Ignore the error Xcode will show here; you'll fix it shortly. But you might wonder, "Why did I designate SceneDelegate
to do this instead of the view controller?"
iOS monitors the geofences registered by an app at all times, including when the app isn't running. If the device triggers a geofence event while the app isn't running, iOS relaunches the app in the background. This makes SceneDelegate
an ideal entry point for handling the event, as the view controller may not be loaded or ready.
Now you might also wonder, "How will a newly created CLLocationManager
instance know about the monitored geofences?"
It turns out that all geofences registered for monitoring by your app are conveniently accessible by all location managers in your app, so it doesn't matter where you initialize the location managers. Pretty nifty, right? :]
Comings and Goings
Now all that's left is to implement the relevant delegate methods to react to the geofence events. To receive those events, add the following extension to SceneDelegate.swift:
// MARK: - Location Manager Delegate
extension SceneDelegate: CLLocationManagerDelegate {
func locationManager(
_ manager: CLLocationManager,
didEnterRegion region: CLRegion
) {
if region is CLCircularRegion {
handleEvent(for: region)
}
}
func locationManager(
_ manager: CLLocationManager,
didExitRegion region: CLRegion
) {
if region is CLCircularRegion {
handleEvent(for: region)
}
}
func handleEvent(for region: CLRegion) {
print("Geofence triggered!")
}
}
As the method names aptly suggest, you receive locationManager(_:didEnterRegion:)
when the device enters a CLRegion
and locationManager(_:didExitRegion:)
when the device exits a CLRegion
.
Both methods receive the CLRegion
in question. You must ensure it's a CLCircularRegion
, since it could be a CLBeaconRegion
if your app happens to be monitoring iBeacons too. If the region is indeed a CLCircularRegion
, call handleEvent(for:)
.
At this point, handleEvent(_:)
takes in a CLRegion
and logs a statement. Not to worry — you'll implement the event handling later.
Simulating Location Events
Now that your app is able to receive geofence events, you're ready to give it a (maybe literal) test run. If that doesn't excite you, it ought to, because for the first time in this tutorial, you're going to see some results. :]
The most accurate way to test your app is to deploy it on your device, add some geotifications and take the app for a walk or a drive. However, it wouldn't be wise to do so right now, as you wouldn't be able to verify the print logs emitted by the geofence events with the device unplugged. Besides, it'd be nice to get assurance that the app works before you commit to taking it for a spin.
Fortunately, there's an easy way do this without leaving the comfort of your home. Xcode lets you include a hard-coded waypoint GPX file in your project that you can use to simulate test locations. The starter project includes one for your convenience. :]
Open SimulatedLocations.gpx, which you can find in the Supporting Files group, and inspect its contents.
You'll see the following:
<?xml version="1.0"?>
<gpx version="1.1" creator="Xcode">
<wpt lat="37.3349285" lon="-122.011033">
<name>Apple</name>
<time>2014-09-24T14:00:00Z</time>
</wpt>
<wpt lat="37.422" lon="-122.084058">
<name>Google</name>
<time>2014-09-24T14:00:05Z</time>
</wpt>
</gpx>
The GPX file is essentially an XML file that contains two waypoints: Google's Googleplex in Mountain View, and Apple Park in Cupertino. You'll notice there are time nodes on each waypoint. They're spaced at five seconds apart, so when you simulate locations with this file, it'll take five seconds to go between Apple and Google. There are also two more GPX files: Google.gpx and Apple.gpx. These are fixed locations, and you may use them for convenience when creating geofences.
Going on an Imaginary Drive
To begin simulating the locations in the GPX file, build and run the project. When the app launches the main view controller, go back to Xcode, select the Location icon in the Debug bar, and choose SimulatedLocations:
Back in the app, use the zoom button in the top-left of the navigation bar to zoom to the current location. Once you get close to the area, you'll see the location marker moving from the Googleplex to Apple Park and back.
Test the app by adding a few geotifications along the path defined by the two waypoints. If you added any geotifications earlier in the tutorial before you enabled geofence registration, those geotifications won't work, so you might want to clear them out and start anew.
For the test locations, it's a good idea to place a geotification roughly at each waypoint. Here's a possible test scenario:
- Google: Radius: 1000m, Message: "Say Bye to Google!", Notify on Exit
- Apple: Radius: 1000m, Message: "Say Hi to Apple!", Notify on Entry
Once you've added your geotifications, you'll see a log in the console each time the location marker enters or leaves a geofence. If you activate the home button or lock the screen to send the app to the background, you'll also see the logs each time the device crosses a geofence, though you won't be able to verify that behavior.