iBeacon Proximity

This recipe shows how to notify Kumulos when an iBeacon is relatively close to the user (i.e. near or immediate).

In order for iOS to allow your app to perform location tracking you must enable those capabilities in your plist and provide a reason to be presented to the user explaining why your app will track their location.

The following Swift class implements the CLLocationManagerDelegate protocol and uses this to track arrival into a BeaconRegion; begin ranging for specific beacons and then notify Kumulos of iBeacons entering immediate proximity.

Your app should create an instance of this class and hold a reference to it for the lifecycle of the app.

//
//  MyLocationManagerDelegate.swift
//  MyProject
//
//  Created by Kumulos
//  Copyright © Kumulos Ltd. All rights reserved.
//
import Foundation
import CoreLocation
import KumulosSDK

class MyLocationManagerDelegate: NSObject, CLLocationManagerDelegate {

    let locationManager = CLLocationManager()
    var beaconsInPreviousCallback = [CLBeacon]()

    override init() {
        super.init()

        // Notify the location manager that this class is its delegate
        locationManager.delegate = self

        // Request location updates from the user
        locationManager.requestAlwaysAuthorization()

        // Start monitoring for iBeacon regions of interest
        startTrackingBeaconRegion()
    }

    private func startTrackingBeaconRegion() {
        // Here we tell the location manager about the beacon(s) we are interested in, in this case any iBeacon that has a UUID matching that of our organisation.
        // This will trigger a callback to didEnterRegion below when any beacon matching the region criteria is detected in proximity.
        let beaconRegion = CLBeaconRegion(proximityUUID: UUID(uuidString: "my-beacons-uuid")!, identifier: "my-beacon-group")
        locationManager.startMonitoring(for: beaconRegion)
    }

    // This callback can be used by the OS to wake-up the app from its background state for a few seconds in order to perform some processing
    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        // This tells us that one or more iBeacons matching our region criteria is nearby.
        // This in itself is not a lot of information to go on, so we will start ranging the beacons in the region to get more detail.
        locationManager.startRangingBeacons(in: region as! CLBeaconRegion)

    }

    // This callback can be useful in development if you start the app already within range of a beacon region
    func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
       let beaconRegion = region as! CLBeaconRegion

       if state == CLRegionState.inside {
           locationManager.startRangingBeacons(in: beaconRegion)
       }
   }

    // This callback is triggered roughly once per second while the app is awake, either if the app is in the foreground or has been awakened by the OS above.
    func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
        // Each iBeacon in range is provided and has a UUID, major and minor identifier, along with a proximity value from the CLProximity enum
        // Naively notifying Kumulos of proximity to every beacon, every second will be network and battery intensive. It can also complicate triggering automations as a result.
        // Therefore, we will set some rules for when to notify Kumulos.
        let filteredBeacons = filterBeaconsForSending(beacons: beacons);
        for beacon in filteredBeacons {
            Kumulos.sendiBeaconProximity(beacon: beacon)
        }
    }

    private func filterBeaconsForSending(beacons: [CLBeacon]) -> [CLBeacon] {
        var beaconsForSending = [CLBeacon]()

        for beacon in beacons {
            // Beacon proximity as defined by the CLProximity enum can be one of:
            //
            // unknown      The proximity of the beacon could not be determined.
            // immediate    The beacon is in the user’s immediate vicinity.
            // near         The beacon is relatively close to the user.
            // far          The beacon is far away.
            //
            // If you only want to know about beacons that are relatively close to the user, uncomment the following lines of code.
            // Please be aware that the OS will only wake-up the app and start ranging for a few seconds when the region is first entered.
            // If the beacon does not come into the user's immediate vicinity within that time, the app will not be able to notify Kumulos.
            // How to handle this depends on the use-case for your app and how the beacons have been provisioned (e.g. do beacon ranges physically overlap or do beacons share the same major identifier etc).
            //
            // if beacon.proximity == CLProximity.far || beacon.proximity == CLProximity.unknown {
            //    continue
            // }

            // If the beacon was already in the collection, at the same range, do not send again
            if beaconHasBeenSent(beacon: beacon) {
                continue
            }

            beaconsForSending.append(beacon);
        }

        // Remember which beacons were processed in this callback (so we don't notify Kumulos again)
        beaconsInPreviousCallback = beacons

        return beaconsForSending
    }

    private func beaconHasBeenSent(beacon: CLBeacon) -> Bool {
        for checkBeacon in beaconsInPreviousCallback {
            if checkBeacon.proximityUUID == beacon.proximityUUID && checkBeacon.major == beacon.major && checkBeacon.minor == beacon.minor {
                return true
            }
        }
        return false
    }
}