NEW: Master Swift design patterns with my latest book! >>

How to detect iBeacons

Written by Paul Hudson    @twostraws

Detecting iBeacons requires a number of steps. But first you need to decide whether you want to detect beacons only when your app is running, or whether you want beacons to be detected even if your app isn't in the background.

Have you decided? Good, because you need to set one of two keys in your Info.plist depending on your choice. If you want to detect beacons only when your app is running, add the key NSLocationWhenInUseUsageDescription and a short string explaining how you'll use the location, e.g. "We want to detect where you are in our store."

If you want the app to detect beacons even when it isn't running (a feat accomplished by handing control of scanning over to the OS), you should use the NSLocationAlwaysUsageDescription key instead.

With that done, we can start to scan for beacons. Open your class in Xcode (it could be a view controller, but it doesn't have to be), then import the Core Location framework like this:

import CoreLocation

Now tell Swift that your class conforms to the CLLocationManagerDelegate protocol so that you can start to receive location updates. If you're using a view controller subclass, your code will look something like this:

class ViewController: UIViewController, CLLocationManagerDelegate {

iBeacon tracking is done using the CLLocationManager class, which is also responsible for requesting location permission from users. You need to create a property for this in your class so that you can store the active location manager, so add this:

var locationManager: CLLocationManager!

If you're using a view controller, you'll probably want to initialize this property in viewDidLoad(), like this:

override func viewDidLoad() {
    super.viewDidLoad()

    locationManager = CLLocationManager()
    locationManager.delegate = self
    locationManager.requestAlwaysAuthorization()
}

If you're using another type of class, you should amend that appropriately. Note that that calls requestAlwaysAuthorization(), which is the correct method to use if you set the NSLocationAlwaysUsageDescription Info.plist key. If you set NSLocationWhenInUseUsageDescription instead, you should use requestWhenInUseAuthorization() instead.

Once you request permission to use your user's location, they'll see an alert with the message you wrote earlier. When they make a choice you'll get a delegate callback called didChangeAuthorization, at which point you can check whether they are authorized you or not:

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    if status == .authorizedAlways {
        if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
            if CLLocationManager.isRangingAvailable() {
                startScanning()
            }
        }
    }
}

Don't worry, we haven't written the startScanning() method yet. If you're using "when in use" mode you should check for .authorizedWhenInUse rather than .authorizedAlways.

Once you've been authorized to scan for iBeacons, you can create CLBeaconRegion objects and pass them to the location manager. Each CLBeaconRegion is uniquely identified by a long number (it's UUID), and optionally also major and minor numbers. As well as monitoring for a beacon's existence, we're also going to ask iOS to range the beacon for us – i.e., tell us how close it thinks we are.

Here's the code:

func startScanning() {
    let uuid = UUID(uuidString: "5A4BCFCE-174E-4BAC-A814-092E77F6B7E5")!
    let beaconRegion = CLBeaconRegion(proximityUUID: uuid, major: 123, minor: 456, identifier: "MyBeacon")

    locationManager.startMonitoring(for: beaconRegion)
    locationManager.startRangingBeacons(in: beaconRegion)
}

Once you're ranging for beacons, you'll get a delegate callback called didRangeBeacons every second or so, at which point you can read a beacon's distance using its proximity value and take appropriate action.

For example, we can make our view change color depending on how far away an iBeacon is with this code:

func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
    if beacons.count > 0 {
        updateDistance(beacons[0].proximity)
    } else {
        updateDistance(.unknown)
    }
}

func updateDistance(_ distance: CLProximity) {
    UIView.animate(withDuration: 0.8) {
        switch distance {
        case .unknown:
            self.view.backgroundColor = UIColor.gray

        case .far:
            self.view.backgroundColor = UIColor.blue

        case .near:
            self.view.backgroundColor = UIColor.orange

        case .immediate:
            self.view.backgroundColor = UIColor.red
        }
    }
}

Available from iOS 7.0 – see Hacking with Swift tutorial 22

Did this solution work for you? Please pass it on!

Other people are reading…

About the Swift Knowledge Base

This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.

Build for watchOS

Take your existing Swift skills to Apple's tiniest platform – check out Hacking with watchOS!

Click here to visit the Hacking with Swift store >>