Editor’s Note: This is Part Two of our series of articles on creating a Swift iBeacon application. By the end of the series you will have written a “game” in Swift called You’re Getting Warmer! which allows you to track down and find a beacon. Part One gets you up and running iBeacon with a Gimbal Proximity Beacon Series 10.
Before getting started with Part Two, you should have an operating iBeacon and know its UUID. If you don’t have an iBeacon start with Part One of this series!
If you’re working with iBeacon we’re just going to assume you known how to create a new Single View Application Swift project with Xcode. Go ahead and do that now. We named our project gettingwarmer
.
Beacon Ranging
There are two methods of listening for iBeacons in iOS: monitoring and ranging. The difference between the two is that monitoring is an activity that can continue running in the background, while ranging is a foreground activity. Radius Networks wrote up a nice explanation describing the differences in detail. Apple’s iBeacon documentation uses the term ranging to describe the activity of determining the proximity of a given iBeacon. Our first application will make use of ranging as opposed to monitoring.
BeaconManager
I have always preferred to use the singleton “manager” pattern for working with CoreLocation in iOS. We’ll create a class called BeaconManager
which will be responsible for an instance of CLLocationManager
as well as serving as our CLLocationManager
delegate. To get started create a new file BeaconManager.swift
in your gettingwarmer
Xcode project and add the following implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import Foundation import CoreLocation class BeaconManager : NSObject, CLLocationManagerDelegate { static let sharedInstance = BeaconManager() var locationManager:CLLocationManager override init() { self.locationManager = CLLocationManager() super.init() self.locationManager.delegate = self self.locationManager.requestWhenInUseAuthorization() } func startRanging() { } // MARK: CLLocationManagerDelegate methods func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) { } func locationManager(manager: CLLocationManager!, didRangeBeacons beacons: [AnyObject]!, inRegion region: CLBeaconRegion!) { } } |
The class declaration and init
routine should be self-explanatory. If you haven’t been working with Swift 1.2 you may have missed the introduction of the static
keyword which allows for a quick implementation of the singleton pattern.
Let’s first implement the didChangeAuthorizationStatus
method, because once we are authorized we want to start ranging.
Here’s our implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// MARK: CLLocationManagerDelegate methods func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) { switch status { case .NotDetermined: self.locationManager.requestWhenInUseAuthorization() case .AuthorizedWhenInUse, .AuthorizedAlways: self.startRanging() case .Denied, .Restricted: NSNotificationCenter.defaultCenter().postNotificationName("LOCATION_DENIED", object: nil) default: break } } |
If the OS can’t determine the authorization status (.NotDetermined
), we’ll simply ask for it again (why not).
If we receive a callback that our status is .AuthorizedWhenInUse
(what we requested) or .AuthorizedAlways
(which will never be the case), we’ll call our startRanging
method.
If on the other hand we receive .Denied
or .Restricted
, we’ll broadcast a notification with NSNotificationCenter
that will be heard by our ViewController
. The ViewController
will then be responsible for raising a dialog indicating that location services are required for the app to operate properly.
Now, let’s take a look at our startRanging
method.
1 2 3 4 5 6 7 |
func startRanging() { let uuid = NSUUID(UUIDString:"788FA98B-871C-4C71-9944-88ADEC84A8DA") let region = CLBeaconRegion(proximityUUID: uuid, identifier: "") self.locationManager.startRangingBeaconsInRegion(region) } |
Ranging begins with the startRangingBeaconsInRegion
method, and in our case a region is identified as “being in the proximity of a beacon broadcasting a UUID of 788FA98B-871C-4C71-9944-88ADEC84A8DA”. It’s that simple. If iOS hears a beacon with this UUID, it is “in” that region.
It may be confusing at first that a “region” in this context is not geographical. Apple’s QuickHelp documentation explains it this way: “A CLBeaconRegion object defines a type of region that is based on the device’s proximity to a Bluetooth beacon, as opposed to a geographic location.”
Once ranging starts we have to be prepared to receive didRangeBeacons
calls on our CLLocationManager
delegate. For now, we’ll add this implementation:
1 2 3 4 5 6 7 8 9 10 |
func locationManager(manager: CLLocationManager!, didRangeBeacons beacons: [AnyObject]!, inRegion region: CLBeaconRegion!) { let beaconsRanged = beacons as! [CLBeacon]! if let beacon = beaconsRanged.last { println("Beacon ranged: \(beacon)") } } |
Before leaving ranging, it is important to draw the distinction between monitoring for regions and ranging beacons. Monitoring can run in the background with notifications sent to your app when the registered CLBeaconRegion
is “entered” (that is, the beacon was heard). Ranging is for finer proximity detection and can only be used while the application is in the foreground. Again, reading the Radius Networks article is useful.
Rounding Out
Thus far we’ve implemented:
- a
BeaconManager
class capable of requesting location services authorization - the
didChangeAuthorizationStatus
method to handle changes in location services authorization from the user - a
startRanging
method which will start listening for our iBeacon - the
didRangeBeacons
method which will be called when iOS hears our iBeacon
We have a few more things to do before running the application:
- setting NSLocationWhenInUseUsageDescription in our
Info.plist
- creating an instance of the
BeaconManager
when ourViewController
appears - intercepting
LOCATION_DENIED
notifications in the event the user declines location services authorization
In your Info.plist
, create a new key called NSLocationWhenInUseUsageDescription
like so:
Next, round out the ViewController
code by adding the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) NSNotificationCenter.defaultCenter().addObserver(self, selector: "locationDenied", name: "LOCATION_DENIED", object: nil) let beaconManager = BeaconManager.sharedInstance } func locationDenied() { let alert = UIAlertController(title:"Permission Required", message:"Location services permission is required.", preferredStyle:.Alert) let ok = UIAlertAction(title: "OK", style: .Default) { (action) -> Void in } alert.addAction(ok) self.presentViewController(alert, animated:true){} } |
When our view appears we want to create an instance of the BeaconManager
which in turn will request authorization. Therefore we add our ViewController
as an NSNotification
observer for the LOCATION_DENIED
notification. If this notification is received we pop up an alert dialog (UIAlertController
) indicating that location services permission is required.
Run It!
If you’ve implemented everything you should now be able to run the application on your iOS device (note: this will not work in a simulator). After granting location services authorization and bringing out our iBeacon we see in the console:
[objc]
Beacon ranged: CLBeacon (uuid:<__NSConcreteUUID 0x1702224e0> 788FA98B-871C-4C71-9944-88ADEC84A8DA, major:1, minor:0, proximity:2 +/- 0.68m, rssi:-59)
[/objc]
What Could Possibly Go Wrong?
Software development can be rewarding and a lot of fun, but it can also drive you crazy when things don’t work right. In this series there are a lot of things that could go wrong that would prevent iOS from recognizing your beacon. If you’re having trouble, consider:
- is your iBeacon programmed with the same UUID as your
CLBeaconRegion
? - is your iBeacon powered?
- was the app granted location services permission?
- is the iBeacon in range?
- is Bluetooth enabled on your iOS device?
Getting the Code
The complete source code for this tutorial is available on BitBucket. The implementation for this article is available on the partTwo
branch.
Next Up
We can now receive iBeacon messages in our application, but we aren’t doing anything with the information. In Part 3 of this series we’ll go through the various properties of the CLBeacon
class, namely:
- proximity
- accuracy
- rssi
We’ll then begin adding a user interface to our view controller for a nice game of You’re Getting Warmer!. Stay tuned!