Geo Targeting
Let’s assume that we want to track our user visiting restaurant. I want to start with project setup. I created new Swift Single View Application project with name GeoTargeting.
Go to Main.storyboard and add new Map View to a ViewController’s view. Create @IBOutlet for this Map View in ViewController.swift. Compiler will show you an error, but wait a minute, we will fix it. Our storyboard is ready. Now we can move to ViewController.swift.
Let’s import Apple’s frameworks MapKit and CoreLocation. And add their protocols to the ViewController. Now ViewController.swift should look like this:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
@IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad( )
}
}
Now we can setup our mapView and create new locationManager.
// 1. create locationManager
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad( )
// 2. setup locationManager
locationManager.delegate = self;
locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
// 3. setup mapView
mapView.delegate = self
mapView.showsUserLocation = true
mapView.userTrackingMode = .Follow
// 4. setup test data
setupData()
}
What we add to the ViewController:
Create an instance of locationManager, which will detect user’s location changes.
Setup locationManager. We set manager’s delegate to the ViewController for track user’s location and monitor regions within ViewController. Also, we configure location tracking for best accuracy.
Setup mapView. We set mapView’s delegate for additional drawing on mapView. Then we ask mapView for showing user’s location to us and to follow this location. This will help us to understand better if user in a region or not. You also can setup MapView’s delegate and showsUserLocation via Storyboard. We did it within the code for better visualisation.
Setup test data. We will add this method later.
Now it’s time to start tracking user’s location. But we don’t know if we have correct access rights. Let’s check it.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// 1. status is not determined
if CLLocationManager.authorizationStatus() == .NotDetermined {
locationManager.requestAlwaysAuthorization()
}
// 2. authorisation were denied
else if CLLocationManager.authorizationStatus() == .Denied {
showAlert("Location services were previously denied. Please enable location services for this app in Settings.")
}
// 3. we do have authorisation
else if CLLocationManager.authorizationStatus() == .AuthorizedAlways {
locationManager.startUpdatingLocation()
}
}
Let’s elaborate this method. First of all, we check authorisation status in viewDidAppear method because we can recheck status after user changed it in settings and come back to our app.
So, which
If status is not determined, then we should ask for authorisation. We ask for “Always” permission because this is the only status which works with CLRegion monitoring.
If authorisation has been denied previously, we should inform the user that our app will work better if he will allow location services. We used method showAlert(title: String) here. This is shortcut method for displaying information UIAlertController with “Cancel” button. We will use this method few times more later.
If we have authorisation we can start the standard location service updating.
After adding this key you can run app once again. Now system will prompt authorisation alert with our description.
Of course, there are few more statuses of the location authorisation, that our app may have. But this is basic which we need for this tutorial.
Now we are ready to monitor regions, let’s add them.
func setupData() {
// 1. check if system can monitor regions
if CLLocationManager.isMonitoringAvailableForClass(CLCircularRegion.self) {
// 2. region data
let title = "City Name Here"
let coordinate = CLLocationCoordinate2DMake(37.703026, -121.759735)
let regionRadius = 300.0
// 3. setup region
let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: coordinate.latitude,
longitude: coordinate.longitude), radius: regionRadius, identifier: title)
locationManager.startMonitoringForRegion(region)
// 4. setup annotation
let restaurantAnnotation = MKPointAnnotation()
restaurantAnnotation.coordinate = coordinate;
restaurantAnnotation.title = "\(title)";
mapView.addAnnotation(restaurantAnnotation)
// 5. setup circle
let circle = MKCircle(centerCoordinate: coordinate, radius: regionRadius)
mapView.addOverlay(circle)
} else {
print("System can't track regions")
}
}
// 6. draw circle
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer
{
let circleRenderer = MKCircleRenderer(overlay: overlay)
circleRenderer.strokeColor = UIColor.redColor()
circleRenderer.lineWidth = 1.0
return circleRenderer
}
Let’s understand together what we did in setupData() method step by step.
You always should check if region monitoring is supported on the user’s device. isMonitoringAvailableForClass will be false if user denied location request, user disabled Background App Refresh or if device is in Airplane mode.
For better visualisation we added annotation in the center of our region.
For better visualisation we added circle on the map, which represent region’s boundaries.
It is a MKMapViewDelegate’s method for drawing our circle.
That’s it with project setup. We are ready for region monitoring.
Apple’s CLRegion
In this tutorial we are working with geographical regions. Apple’s CLRegion – is an circle area of a specified radius around a known location. So, with CLRegion you can monitor only circle areas. Let’s add some code for tracking regions.
// 1. user enter region
func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
showAlert("enter \(region.identifier)")
}
// 2. user exit region
func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
showAlert("exit \(region.identifier)")
}
We added two methods for notifying user that he cross region’s boundaries. We use our alert shortcut method for visualise it. The system will fire this methods only when the boundary plus a system-defined cushion distance is crossed.
When the user’s location changes, you can remove regions that are now farther way and add regions coming up on the user’s path. If reach regions limit, location manager will fire monitoringDidFailForRegion method. You can handle it for better app’s UX.
Just two methods and a lot of limitation to work with them which we should always keep in mind.
We set up all methods we need for working with simple regions. Now you can go to your car and try to drive through your region. Just kidding. There is another convenient way to test this functionality.
How to Test Regions
There is the convenient way how to do it with Xcode. We will use GPX files. GPX is an XML schema described common GPS data format for using in software development. It is easier to show it than describe it.
<?xml version="1.0"?>
<gpx version="1.1" creator="Xcode">
<wpt lat="37.702801" lon="-121.751862"></wpt>
<wpt lat="37.702821" lon="-121.752321"></wpt>
<wpt lat="37.702840" lon="-121.752780"></wpt>
</gpx>
In this example GPX we put three points near our region. Xcode will take it one-by-one and move user’s location. One second per one point.
Now we can run application, go back to Xcode → Open Debug Area → Choose “Simulate location” → Choose America. Go back to simulator, user’s location should start moving.
After few seconds you will see alert “enter City Name Here”, after few more seconds “exit City Name Here”. So, our regions finally works! Congratulations!
Complex Business Logic in Your Region
For some applications enter/exit events is enough. But what if you want track more complicated logic? Maybe you are interesting how long user where in a region or user’s average speed while he was in the region. In this example we will check if user were enough time in our restaurant, which means he is a visitor and we can ask him for his feedback. Let’s add some improvements into our project.
// 1.
var monitoredRegions: Dictionary<String, NSDate> = [:]
func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
showAlert("enter \(region.identifier)")
// 2.1. Add entrance time
monitoredRegions[region.identifier] = NSDate()
}
func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
showAlert("exit \(region.identifier)")
// 2.2 Remove entrance time
monitoredRegions.removeValueForKey(region.identifier)
}
// 3. Update regions logic
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
updateRegions()
}
We will store user’s entrance time in dictionary.
Here I implement adding/deleting region’s entrance time.
Location manager’s delegate method didUpdateLocations will help us check is user already spend enough time in our region.
func updateRegions() {
// 1.
let regionMaxVisiting = 10.0
var regionsToDelete: [String] = []
// 2.
for regionIdentifier in monitoredRegions.keys {
// 3.
if NSDate().timeIntervalSinceDate(monitoredRegions[regionIdentifier]!) > regionMaxVisiting {
showAlert("Thanks for visiting our restaurant")
regionsToDelete.append(regionIdentifier)
}
}
// 4.
for regionIdentifier in regionsToDelete {
monitoredRegions.removeValueForKey(regionIdentifier)
}
}
Let’s assume 10 seconds is enough time for user. Also, we need variable for storing regions’ where user were enough time for future deleting this regions, as they already did what we want.
Now we go through all currently monitored regions.
If user already enough time, we will show to user special message and mark this region as ready for deleting.
And delete all regions, where user was enough time.
Now you can implement absolutely any complex login within updateRegionsMethod.
Custom Regions
It’s really hard to explain to users that their life will be better if they allow your app to use location always. That’s why sometimes you want to track regions only while user run your app. I suggest to create your custom region class, with similar interface and callback methods like CLRegion. So, it will be easier to understand what you class do for you and any other develop how already familiar with CLRegion.
protocol RegionProtocol {
var coordinate: CLLocation {get}
var radius: CLLocationDistance {get}
var identifier: String {get}
func updateRegion()
}
protocol RegionDelegateProtocol {
func didEnterRegion()
func didExitRegion()
}
You can use this protocols for easily move from CLRegion to your custom class.
One more CLRegion limitation is that we can track only circle areas. Sometimes, we want monitor polygon area (square, pentagon, etc.) or oval area. We also can do it with our custom class. You just need to check this condition in didEnterRegion Method of our RegionDelegateProtocol.
Also, it is not necessary to show some alerts to user immediately. There are lot of use cases when we want store this data for future data analysis.
For the complete Xcode project, you can get it here.
Comments
Post a Comment
Thank You.