SDK

Location Services

In this page you'll dive deeper into functionality like using GPS signals to get the user's location or monitor their visits to regions and proximity to BTLE devices.

These services will bring a new level of contextuality to your app, allowing you to create geo-triggers to send notifications or categorize users based on their location behaviour.

Tracking a user location in iOS will require the user's authorization. This means that your app must declare usage description texts explaining why it needs to track location. It is mandatory the use of the key NSLocationAlwaysUsageDescription. In iOS 11, a new permission was introduced, and you are required to include both NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription in your Info.plist file. In short, if you want to support all versions, you need to include the following:

<plist version="1.0">
<dict>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>We will need to make use of your location to present relevant information about offers around you.</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>We will need to make use of your location to present relevant information about offers around you.</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>We will need to make use of your location to present relevant information about offers around you.</string>
</dict>
</plist>

Make sure you provide a text that best describes why and how you are going to use the user’s location. This text will be displayed to the user the first time you request the use of location services. Users are more likely to trust your app with location data if they understand why you need it.

As mentioned in the Implementation page, if you are going to use location services, you must include the NotificareGeoKit dependency.

Requesting Permissions

Prior to leveraging the user's location, your app is responsible to request the user's authorization. Although CoreLocation allows you to immediately request Always authorization, the user will get the While In Use prompt, and later on the Always upgrade prompt. To have more control over the flow, it is recommended to start by requesting While In Use authorization, which makes your app capable of tracking the user's location. However, to support geofencing and beacons, you need to acquire the Always authorization status.

It is important to note the Notificare library is not notified of authorization status changes, therefore you have to call Notificare.shared.geo().enableLocationUpdates() on every authorization status change. This allows us to check the latest authorization and act accordingly by enabling location tracking, geofencing or clearing up location data based on the user's decision.

func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
    // handle the change in authorization.

    Notificare.shared.geo().enableLocationUpdates()
}

In order to achieve what was mentioned above, here is a rough implementation to serve as a guide. You'll have to adjust this sample to match your application.

import CoreLocation
import NotificareKit

class LocationController: NSObject, CLLocationManagerDelegate {
    /// CoreLocation limits the amount of times an app can request the Always authorization to once per session.
    /// We should keep track of that request to prompt the user to grant us access via the Settings app instead.
    private(set) static var hasRequestedAlwaysPermission = false

    private let locationManager: CLLocationManager

    var hasLocationTrackingCapabilities: Bool {
        let hasLocationUpdatesEnabled = Notificare.shared.geo().hasLocationServicesEnabled
        let hasLocationPermissions = locationManager.authorizationStatus == .authorizedWhenInUse || locationManager.authorizationStatus == .authorizedAlways

        return hasLocationUpdatesEnabled && hasLocationPermissions
    }

    var hasGeofencingCapabilities: Bool {
        let hasLocationUpdatesEnabled = Notificare.shared.geo().hasLocationServicesEnabled
        let hasAlwaysPermission = locationManager.authorizationStatus == .authorizedAlways

        return hasLocationUpdatesEnabled && hasAlwaysPermission
    }

    override init() {
        self.locationManager = CLLocationManager()
        super.init()

        self.locationManager.delegate = self
    }

    func requestPermissions() {
        switch locationManager.authorizationStatus {
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        case .restricted:
            break
        case .denied:
            // TODO: Present the Settings prompt.
            break
        case .authorizedAlways:
            Notificare.shared.geo().enableLocationUpdates()
        case .authorizedWhenInUse:
            if LocationController.hasRequestedAlwaysPermission {
                Notificare.shared.geo().enableLocationUpdates()
            } else {
                LocationController.hasRequestedAlwaysPermission = true
                locationManager.requestAlwaysAuthorization()
            }
        case .authorized:
            // Deprecated, not applicable.
            break
        @unknown default:
            break
        }
    }

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        switch manager.authorizationStatus {
        case .denied:
            // To clear the device's location in case one has been acquired.
            Notificare.shared.geo().enableLocationUpdates()

            // TODO: Possibly handle the user's rejection.
        case .authorizedAlways:
            // Enable geofencing & beacon support.
            Notificare.shared.geo().enableLocationUpdates()
        case .authorizedWhenInUse:
            // Enable location tracking.
            Notificare.shared.geo().enableLocationUpdates()

            // Try upgrading to always.
            LocationController.hasRequestedAlwaysPermission = true
            locationManager.requestAlwaysAuthorization()
        default:
            break
        }
    }
}

To make sure the location updates aren't started before the library is ready to be used, you should wait until it is safe to do so, by listening to the ready event. Once you have requested the appropriate permissions, and they have been granted, you can call Notificare.shared.geo().enableLocationUpdates() to start receiving location updates. Our library will automatically collect the user location and start monitoring for regions you've created via the dashboard or API.

You can also check whether the user enrolled on location updates.

Notificare.shared.geo().hasLocationServicesEnabled

Location Services Delegates

In order for you to update your app's UI, react programmatically to location updates or any other location services event you might want to use, you can implement the following delegates. Please note that the implementation of these delegates is optional and only required if your app wants to provide custom functionality.

class MainViewController : UIViewController, NotificareGeoDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        Notificare.shared.geo().delegate = self
    }

    func notificare(_ notificareGeo: NotificareGeo, didUpdateLocations locations: [NotificareLocation]) {
      //Handle location updates
    }

    func notificare(_ notificareGeo: NotificareGeo, didFailWith error: Error) {
      //Handle errors from location manager
    }

    func notificare(_ notificareGeo: NotificareGeo, didStartMonitoringFor region: NotificareRegion) {
      //Handle monitoring started events for a geo-zone
    }

    func notificare(_ notificareGeo: NotificareGeo, didStartMonitoringFor beacon: NotificareBeacon) {
      //Handle monitoring started events for a beacon
    }

    func notificare(_ notificareGeo: NotificareGeo, monitoringDidFailFor region: NotificareRegion, with error: Error) {
      //Handle monitoring failed events for a geo-zone
    }

    func notificare(_ notificareGeo: NotificareGeo, didDetermineState state: CLRegionState, for region: NotificareRegion) {
      //Handle geo-fence state events
    }

    func notificare(_ notificareGeo: NotificareGeo, didEnter region: NotificareRegion) {
      //Handle geo-zone entered events
    }

    func notificare(_ notificareGeo: NotificareGeo, didExit region: NotificareRegion) {
      //Handle geo-zone exit events
    }
}

Using Bluetooth Low-Energy Beacons

Once you've implemented the location functionality, we will automatically monitor BTLE beacons in the user's vicinity.

However, you might want to retrieve the proximity level of any beacons around you. This is only possible while the app is in foreground. To get this information you will need to implement the following listener:

class MainViewController : UIViewController, NotificareGeoDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        Notificare.shared.geo().delegate = self
    }

    func notificare(_ notificareGeo: NotificareGeo, monitoringDidFailFor beacon: NotificareBeacon, with error: Error) {

    }

    func notificare(_ notificareGeo: NotificareGeo, didRange beacons: [NotificareBeacon], in region: NotificareRegion) {

    }

    func notificare(_ notificareGeo: NotificareGeo, didFailRangingFor region: NotificareRegion, with error: Error) {

    }

    func notificare(_ notificareGeo: NotificareGeo, didDetermineState state: CLRegionState, for beacon: NotificareBeacon) {

    }

    func notificare(_ notificareGeo: NotificareGeo, didEnter beacon: NotificareBeacon) {

    }

    func notificare(_ notificareGeo: NotificareGeo, didExit beacon: NotificareBeacon) {

    }
}

This is not a mandatory step in order to use beacons, as geo-triggers created in the dashboard will trigger notifications whenever you are in the vicinity of a beacon, even when your app is not being used.

Using Visits API

As part of our location services feature, you will be able to use Apple's Visits API. This API will allow you to get events whenever users visit a location. This might be a great way to discover which areas a user visits. To use this feature you will need to turn the VISITS_API_ENABLED property to YES in your app's NotificareOptions.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>VISITS_API_ENABLED</key>
	<true/>
</dict>
</plist>

When enabled, as soon as you start location updates you will be receiving visits events too. To handle those visits make sure you implement the following delegate method:

func notificare(_ notificareGeo: NotificareGeo, didVisit visit: NotificareVisit) {

}

Using Heading API

Also part of our location services feature, Apple's Heading API will allow you to obtain the user's current heading. To use this feature you will need to turn the HEADING_API_ENABLED property to YES in your app's NotificareOptions.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>HEADING_API_ENABLED</key>
	<true/>
</dict>
</plist>

When enabled, as soon as you start location updates you will be receiving heading events too. To handle those events make sure you implement the following delegate method:

func notificare(_ notificareGeo: NotificareGeo, didUpdateHeading heading: NotificareHeading) {

}

Checking the monitoring state

There are some use cases when it's useful to know what is the current state of the monitoring service. This can be particularly helpful during development. You can check, on-demand, which regions are being monitored like the following.

Notificare.shared.geo().monitoredRegions

Similarly, you can also check which regions the device is currently inside of.

Notificare.shared.geo().enteredRegions

Disable Location

The same way you enable location, you can stop tracking the user location by invoking the following method:

Notificare.shared.geo().disableLocationUpdates()