Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Shining a Swift Light on iBeacons

DZone's Guide to

Shining a Swift Light on iBeacons

This article will not only help you on the path to adopting beacons into your own apps, but also identify some of the research and problem solving along the way.

· IoT Zone
Free Resource

Discover why Bluetooth mesh is the next evolution of IoT solutions. Download the mesh overview.

In creating many proof-of-concept mobile apps and then presenting the apps at various events, we often get thanks from the audience but "can you please give me an article I can read to learn from your research and see how you solved the problem?"

This week's demo de jour (French was never my forté) is an iOS Swift iBeacon app designed to show a message when, say, our mobile user approaches a beacon identifying a theoretical shop "Chris's Emporium".  This is a pretty common use case for beacons, but in building this I did discover that iOS's CoreLocation library doesn't make this entirely simple in the way that it raises events on our mobile user entering and exiting the beacon's range.  In addition NSLocationAlwaysUsageDescriptionone of my goals was to not hard code the beacon identifier and message in the app, but instead dynamically derive messages from a beacon cloud registry.

So with these two ideas in mind, rather than just creating a demo I thought it would be a good chance to address the common request for more information by writing this article and sharing it on DZone for other's to benefit.

What Is an iBeacon?

Image title

For anyone reading this article out of curiosity and wondering what an iBeacon is, there are plenty of articles available to discuss the concept including a generalized article from Wikipedia, a technical article from MBed that discusses the differences between iBeacons, altBeacons and Eddystone, and an article about how beacons are slowly being adopted into airports as a real use case example.

As this article is specifically about detecting beacons on iOS, I'll focus on iOS's support for Apple's iBeacon standard going forward.

Setting up an iBeacon

When building an iOS app developers typically rely on the iOS Simulator for testing. Unfortunately, the iOS Simulator does not support a simulated iBeacon.

Image title

An alternative is you can setup an iPhone as a beacon itself but in thinking about this, it seemed like too much hassle as I wanted to run both the pseudo-beacon on my phone and the beacon monitoring app at the same time. Instead for my testing purposes, I selected a relatively inexpensive RadBeacon Dot which allowed me to cleanly separate the beacon from the app I'll build to run on my phone.

Image title

RadBeacon Dot's are turned on with one click and they will flash once green. To start working with the Dot I needed to access its various identifiers which the app listens for.  This is done by putting the Dot in admin mode by clicking twice and holding the second click for 5 seconds, upon where the Dot will flash green twice. Then with the RadBeacon companion app available for iPhones & Android, I could see the beacon and its unique identifiers.

Of importance for later in this article is the UUID and major-minor values which identifies the specific beacon.

From here to turn the beacon off again, one click it flashes red, then one click to turn it back on so it starts advertising/transmitting (and of course remember to turn it off at some point too).

The Sample Case

As mentioned earlier my goal was to build a simple app that when it detects the beacon to be in range of my phone, my app raises a message.  If you can imagine, say, I run a range of knick-knack stores called "Chris's Emporium" where customers enter each store in different shopping centers identified by their own unique beacon, the goal would be to ping customers from my app a unique message for that store such as a sale or voucher to entice the customer to enter and buy, buy, buy.

A nuance of iBeacons is that iOS can monitor for a specific beacon by its UUID+major+minor identifiers, each combination of values referred to in iOS terminology as a "region".  However, there is a reported limit to iOS that it can only search for 20 regions at any one time. While this limit may change in the future, it's possible to see Apple's thinking here, they don't want numerous apps on the one mobile device searching for 1000s of beacons draining the device's battery.

This does present a problem for "Chris's Emporium" as with a recent expansion I now have over 100 stores (knick-knacks are hot!), which will mean 100 beacons each with their own UUID+major+minor "region" configuration.  How can I search for 100 individual beacons when iOS only allows 20?

The trick to this is to use the same UUID across my beacons, but different major and minor values.  iOS's Core Location Services then allow me to just monitor for the UUID by itself as a single region.  As long as one of my beacons has the same UUID, regardless of the beacon's major or minor values, it will be detected by my app.

Of course, this presents a separate problem that once I detect a beacon that matches my UUID, I then need to retrieve its major + minor value so I can determine what specific "Chris's Emporium" store the beacon is identifying, and finally I need to look up the message to show for that shop.

iOS Code

In order to work with beacons and other location-based services in iOS, this requires the CoreLocation framework and CLLocationManager module. Apple's Location and Maps Programming Guide provides instructions for how to get location based services working, beacons getting its own section.  I will admit to quite a few StackOverflow searches to understand how it all works, there were a few things I didn't expect, and hopefully, those points will be conveyed in this article for you to benefit too.

There's essentially 6 tasks in code I need to perform in order to work with beacons:

  1. If not already, request permission from the user to monitor for beacons
  2. Check the device is capable of monitoring for beacons
  3. Create a CLBeaconRegion object with the identifiers you wish to listen for
  4. Register a CLLocationManagerDelegate to handle the beacon events & display my message
  5. Start the beacon monitors (where my events will then be detected & handled)
  6. ...and at some later point, stop the beacon monitors

To demonstrate this code I'll use a simple ViewController wired up with buttons.

Starting out with the first task:

import UIKit
import CoreLocation

class ViewController: UIViewController {

  let locationManager = CLLocationManager()

  @IBAction func doRegisterBeacons(sender: AnyObject) {
    locationManager.requestWhenInUseAuthorization()

    let monitoringAuthorized = CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedWhenInUse        
    let monitoringAvailable = CLLocationManager.isMonitoringAvailableForClass(CLBeaconRegion)

    if (!monitoringAuthorized || !monitoringAvailable) {
 // Handle error
    } else {


In starting out with the locationManager I need to request permission from the user to monitor for beacons either using requestWhenInUseAuthorization or requestAlwaysAuthorization calls.  Depending on the use case I can request permission to monitor 'always' even when the application is in the background, or only when the app is in use in the foreground as I've done here. To be clear "always" doesn't mean the app will implicitly listen in the background, we're just asking for permission.  

It's a requirement since iOS 8 that included in the Info.plist file is a String message for NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription respectively, otherwise the app beacon monitoring will fail silently:

Image title


As a result of this code at runtime the user will see the following confirmation:

Image title

Most likely the user will click the Allow option.  Of course, they might not, so I then use the CLLocationManager class method authorizationStatus() to determine if the user failed to give the app the correct permissions, and handle this accordingly as seen a few lines down in the if statement.  For the demo, I chose to keep it simple by printing a console message, but in a real app, you would most likely show an alert asking the user to rectify the issue.

Another further check is a call to isMonitoringAvailableForClass(CLBeaconRegion) that determines if the device is capable of monitoring external beacons (I'll talk more about CLBeaconRegion in a moment).  As the Apple guide points out there are several reasons this call may return false including the device being in Airplane mode.  So unlike my simple example if you choose to implement this you will need to robustly check this flag as settings like Airplane mode are frequently enabled by users which will mean the beacon monitoring will be turned off & none of this will work.

Having checked the outcome of these calls I'm then in a position to start setting up my CLBeaconRegion registration.

CLBeaconRegion at first felt like an odd name as aren't I just searching for a beacon? Why the term region? As explained earlier region is the term for the configuration of the beacon settings, possible a specific UUID+major+minor for 1 beacon, or just a UUID covering many beacons sharing the same UUID but different major+minor values. In a logical sense when the application starts monitoring for the beacon, it is also monitoring for the virtual boundary that the beacon transmits, a "region" as such. These actions raise events through a CLLocationManagerDelegate class that I will explain and create in a moment.

A typical CLBeaconRegion instantiation will look like the following:

let beaconRegion: CLBeaconRegion = CLBeaconRegion(
  proximityUUID: NSUUID(UUIDString: "0AC59CA4-DFA6-442C-8C65-22247851344C"),
  major: 4, minor: 200, identifier: "BeaconRegion")


From the Radbeacon Dot earlier you can see here I'm using the same UUID, major and minor values to search for the beacon.  This isn't ideal however as I've hardcoded the beacon values into my app.  I'll look at a solution for this later.

In addition note the identifier parameter with the String "BeaconRegion".  As my app can theoretically search for all sorts of location service regions, maybe beacon regions, maybe geofences, I need a way in my code later when monitoring for multiple region events, to distinguish the events from one another for different regions.  So the identifier I gave here gives me that capability as the region name will be returned in the event too.

Finally, as stated earlier, I'm not interested in searching for a specific beacon identified by a UUID+major+minor value, rather I want to listen for beacons all sharing the same UUID.  As such I'll rearrange the code as follows to search for the UUID alone:

import UIKit
import CoreLocation

class ViewController: UIViewController {

  let beaconUUID:NSUUID = NSUUID(UUIDString: "0AC59CA4-DFA6-442C-8C65-22247851344C")!
  let regionId: String = "BeaconRegion"

  let locationManager: CLLocationManager = CLLocationManager()
  var beaconRegion: CLBeaconRegion = CLBeaconRegion()
  let beaconDelegate: BeaconDelegate = BeaconDelegate()

  @IBAction func doRegisterBeacons(sender: AnyObject) {
     locationManager.requestWhenInUseAuthorization() 

     let monitoringAuthorized = CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways
     let monitoringAvailable = CLLocationManager.isMonitoringAvailableForClass(CLBeaconRegion)

     if !monitoringAuthorized || !monitoringAvailable {
       // Handle error
     } else {
       beaconRegion = CLBeaconRegion(proximityUUID: beaconUUID, identifier: regionId)

       beaconRegion.notifyOnEntry = true
       beaconRegion.notifyOnExit = true

       locationManager.delegate = beaconDelegate
     }
  }


Note how I set the CLBeaconRegion entries for notifyOnEntry and notifyOnExit.  While these are the default values, I've included these to show that these can be turned on and off, and they control what events my code will listen for when interacting with the beacons, here when the device moves into the region of a beacon (~check-in), or out again (~check-out).

Handling Beacon Events

So talking about events, where and how are these captured by my code?

iOS, in this case, requires that I define a delegate for CLLocationManager of type CLLocationManagerDelegate.  This delegate has an array of methods that will be called on at certain events once I start monitoring for the beacons.  As you can see in the above code I've assigned a variable of instance BeaconDelegate to the locationManager.delegate, and the code below demonstrates what my BeaconDelegate class looks like:

class BeaconDelegate: NSObject, CLLocationManagerDelegate {

  func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
    // Called when device first enters beacon range
  }

  func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
    // Called when device is in beacons signal range and moving
  }

  func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
    // Called when device exits beacon range
  }
}


As the comments suggest, the locationManager:didEnterRegion and locationManager:didExitRegion methods defined from the supertype CLLocationManagerDelegate, are called when the device/app is monitoring for a CLBeaconRegion and the device moves in or out of the beacon range, once for each event.  Conversely the locationManager:inRegion method is called continuously as the device moves within the beacons range.

For my use case for detecting when the user moves into the range of the beacon, I initially thought the locationManager:didEnterRegion and didExitRegion methods would be perfect to handle my use case of displaying a message to the user as they enter my theoretical shop.  But somewhat mysteriously the methods do not get passed any details about the beacon that activated the event, just the region.

So imagine I set up a beacon at the front of my 100 stores, all with the same UUID, but different major/minor value pairs to uniquely identify each beacon at each different shop location.  My app then started monitoring for those beacons via the UUID alone with a region identifier "BeaconRegion".  Now if all I cared about as the mobile user walked into my shop past one of my beacons was to send out a generic "Welcome to Chris's Emporium" message for ALL the shops, in my didEnterRegion & didExitRegion methods I just need to detect the region is the "BeaconRegion" then show a generic message.

However imagine instead I want to show a customized message for each shop, maybe a message like "Welcome to Chris's Emporium at Garden City Shopping Plaza".  To do this, while I can generically monitor for all beacons with the same UUID, once a beacon is found I need to retrieve its major & minor value so I can determine what specific beacon the customer is visiting, then look up which shop and related message to show.

This presents a problem as the didEnterRegion and didExitRegion methods don't get passed any information about the beacon that was detected.  Only the locationManager:inRegion method receives an array of beacons.  The inRegion method also differs from the enter and exit siblings in that it is constantly called as the user moves across the beacons area.

This presents a further problem because the inRegion method will continuously report it can see the in-range beacon, implying if I raised a message to the user each time inRegion is called, they'd be bombarded with messages. My polite "Hello" message may wear thin after sometime, I guess.

As this Stackoverflow thread points out the solution is to handle this with some logic & coordination between didEnterRegion, didExitRegion and inRegion.  The following code is my first attempt at this, using a flag to detect if I've seen the beacon for the first time since the device entered the beacon's region, and only displays a message once:

class BeaconDelegate: NSObject, CLLocationManagerDelegate {

  // Lazy implementation of catering for only working with the beacon the first time detected
  static var firstBeaconFound: Bool = false;

  // Called when device first enters beacon range
  func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
    print("Entering \(region.identifier)")
  }

  // Called when device is in beacons signal range and moving
  func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
    if !BeaconDelegate.firstBeaconFound  && beacons.count > 0 {
      let beacon: CLBeacon = beacons.first!

      if beacon.proximity == CLProximity.Immediate {
        BeaconDelegate.firstBeaconFound = true;
        let messageBody: String = "UUID \(beacon.proximityUUID.UUIDString) major \(beacon.major.stringValue) minor \(beacon.minor.stringValue)"
        print("Beacon detected \(messageBody)") // or an alert
      }
    }
  }

  // Called when device exits beacon range
  func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
    print("Exiting \(region.identifier)")      
    if region.identifier == "BeaconRegion" {
      reset()
    }
  }

  func reset() {
    BeaconDelegate.firstBeaconFound = false
  }
}


As you can see in the locationManager:inRegion method I detect if I've seen the beacon before, if not I raise a message, but if I have I ignore, until the mobile user leaves the region and I reset the flag.  Admittedly this code isn't ideal as if the user walks in/out, then in/out of my shop again identified by the beacon, they'll get multiple notifications. So the code should likely remember how long since it last saw the beacon and not raise a message if it's, say, in the last day.  Remember we're building a PoC here & so some shortcomings are ok in my demo app, ultimately I'm demonstrating the learning path, PoCs aren't production-ready apps (thank goodness).

Moving on, once I've setup the delegate to handle the events this is the right time to kick off the monitoring for beacons:

@IBAction func doStartBeaconMonitoring(sender: AnyObject) {
  // When beacon detected, invokes CLLocationManagerDelegate didEnterRegion/didExitRegion
  locationManager.startMonitoringForRegion(beaconRegion)
  // When beacon detected, invokes CLLocationManagerDelegate inRegion
  locationManager.startRangingBeaconsInRegion(beaconRegion)
}


The locationManager provides different methods for doing this.  Calling startMonitoringForRegion will raise the didEnterRegion & didExitRegion events in the delegate, while I also need startRangingBeaconsInRegion for inRegion to be called.  As you can appreciate from my previous discussion once the beacon is detected, because the inRegion method will be constantly called, this will have an associated cost with CPU usage and battery, particularly if I set this to run in the background.  Potentially the better approach would be to only enable startRangingBeaconsInRegion when the delegate didEnterRegion method is called, and turn it off again when the mobile user exits via didExitRegion.

As this is a PoC, instead I chose to be lazy and just provide a manual way to turn the monitoring off.

@IBAction func doStopBeaconMonitoring(sender: AnyObject) {
  locationManager.stopMonitoringForRegion(beaconRegion)
  locationManager.stopRangingBeaconsInRegion(beaconRegion)
  beaconDelegate.reset()
}


Overall here's the summary sample code, with a minor modification in the locationManager:didRangeBeacons methods to raise an alert when the beacon is seen, and I also moved the regionId to a struct Constants so it's accessible across the code rather than hardcoded in several places as a String:

import UIKit
import CoreLocation

struct Constants {
    static let RegionId = "BeaconRegion"
}

class BeaconDelegate: NSObject, CLLocationManagerDelegate {

  // Lazy implementation catering for working with the beacon 1st time detected
  static var firstBeaconFound: Bool = false;

  // Called when device first enters beacon range
  func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
    print("Entering \(region.identifier)")
  }

  // Called when device is in beacons signal range and moving
  func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
    if !BeaconDelegate.firstBeaconFound && beacons.count > 0 {
      let beacon: CLBeacon = beacons.first!

      if beacon.proximity == CLProximity.Immediate {
        BeaconDelegate.firstBeaconFound = true;
        let messageBody: String = "UUID \(beacon.proximityUUID.UUIDString) major \(beacon.major.stringValue) minor \(beacon.minor.stringValue)"

        let alert = UIAlertController(title: "Beacon detected", message: messageBody, preferredStyle: UIAlertControllerStyle.Alert)
        alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default,handler: nil))
        UIApplication.sharedApplication().windows[0].rootViewController?.presentViewController(alert, animated: true, completion: nil)
      }
    }
  }

  // Called when device exits beacon range
  func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
    print("Exiting \(region.identifier)")

    if region.identifier == Constants.RegionId {
      reset()
    }
  }

  func reset() {
    BeaconDelegate.firstBeaconFound = false
  }
}

class ViewController: UIViewController {

  let beaconUUID:NSUUID = NSUUID(UUIDString: "0AC59CA4-DFA6-442C-8C65-22247851344C")!

  let locationManager: CLLocationManager = CLLocationManager()
  var beaconRegion: CLBeaconRegion = CLBeaconRegion()
  let beaconDelegate: BeaconDelegate = BeaconDelegate()

  @IBAction func doRegisterBeacons(sender: AnyObject) {

    locationManager.requestWhenInUseAuthorization() // or locationManager.requestAlwaysAuthorization()

    let monitoringAuthorized = CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedWhenInUse
    let monitoringAvailable = CLLocationManager.isMonitoringAvailableForClass(CLBeaconRegion)

    if (!monitoringAuthorized || !monitoringAvailable) {
      // Handle error
    } else {
      beaconRegion = CLBeaconRegion(proximityUUID: beaconUUID, identifier: Constants.RegionId)

      beaconRegion.notifyOnEntry = true
      beaconRegion.notifyOnExit = true

      locationManager.delegate = beaconDelegate
    }
  }

  @IBAction func doStartBeaconMonitoring(sender: AnyObject) {
    // When beacon detected, invokes CLLocationManagerDelegate didEnterRegion/didExitRegion
    locationManager.startMonitoringForRegion(beaconRegion)
    // When beacon detected, invokes CLLocationManagerDelegate inRegion
    locationManager.startRangingBeaconsInRegion(beaconRegion)
  }

  @IBAction func doStopBeaconMonitoring(sender: AnyObject) {
    locationManager.stopMonitoringForRegion(beaconRegion)
    locationManager.stopRangingBeaconsInRegion(beaconRegion)
    beaconDelegate.reset()
  }
}


With the code deployed and running on my iPhone, the registration code invoked then the monitoring turned on, and in moving into range of my Radbeacon Dot iBeacon (turned on of course) after a few seconds the app detected the beacon and I see the following message.

Image title

With the code deployed and running on my iPhone, the registration code invoked then the monitoring turned on, and in moving into range of my Radbeacon Dot iBeacon (turned on of course) after a few seconds the app detected the beacon and I see the following message you can see in the picture here.

And a little tip at this point: remember to turn the monitoring off too otherwise your iPhone's battery life will suffer ... as I discovered.

Using a Beacon Registry for Dynamic Beacon Identities & Messages

The above code is neat & simple but does have some shortcomings. Firstly, the beacon UUID is hardcoded.  In the future if I choose to use another UUID this hardcoding will mean another deployment of my app through the App Store, not ideal as this will take a long time and there's no guarantee customers will download the update.  In addition, when I detect a specific beacon matching the UUID, but with its own major/minor values which I'm using to uniquely identify beacons on entry to different shops, how do I provide a custom message for that shop without hardcoding those messages too?

A better way to handle this would be to have a dynamic beacon registry that provides the context information I need. This registry service — or location service — ideally lives in the cloud so it is accessible to everyone. One example of such a cloud-based location service is Oracle Mobile Cloud Service (MCS).

Ideally, what I want from such a beacon registry is the ability to store "places", ie. static locations like a shop, "assets", i.e. movable places like a forklift that I want to track, and "beacons" which are used to identify the "place" or "asset".  In addition places should be hierarchical, so "Chris's Emporium" is a parent place, and has several associated child stores/places such as "Chris's Emporium at Garden City Shopping Plaza." And, of course, the solution should be mobile client agnostic in that it supports all sorts of beacon technologies, not just limited to Apple's iBeacons, but also altBeacons and so on.

So in my case, I can generically setup a parent place "Chris's Emporium" with an iBeacon with just UUID 0AC59CA4-DFA6-442C-8C65-22247851344C. As a child place to "Chris's Emporium" I can then setup "Chris's Emporium at Garden City Shopping Plaza" with the same UUID 0AC59CA4-DFA6-442C-8C65-22247851344C, but now I define a specific major/minor value pair of 4/200 to identify this shop beacon from the others.  I also then include a custom "message" attribute to show to the mobile user when they approach this shop

Oracle's MCS provides a native iOS SDK saving me writing raw REST API calls against the cloud beacon registry.  As the SDK does most of the hard coding grunt work I'll continue with it from here (my days of getting excited about REST are long gone).

To load the required beacon data for my app from MCS I introduced a new method to call the SDK to query back the place "Chris's Emporium" to get the parent UUID:

@IBAction func doLoadMcsData(sender: AnyObject) {
  let mcsMbe: OMCMobileBackend = OMCMobileBackendManager.sharedManager().defaultMobileBackend!
  let loginError: NSError! = mcsMbe.authorization.authenticateAnonymous()
  // TODO: Handle loginError

  let queryPlace: OMCLocationPlaceQuery = mcsMbe.location().buildPlaceQuery()

  queryPlace.name = "Chris's Emporium"
  queryPlace.limit = 1
  queryPlace.orderByAttribute = OMCLocationDeviceContainerQueryOrderByAttributeType.Name
  queryPlace.orderByOrder = OMCLocationObjectQueryOrderByOrderType.Ascending

  queryPlace.executeWithCompletionHandler { (placesResult, placesError) in
    // TODO: Handle placesError
    let place: OMCLocationPlace = placesResult!.items.first as! OMCLocationPlace

    place.devicesWithCompletionHandler{ (devicesResult, devicesError) in
      let beacon: OMCLocationIBeacon = devicesResult!.first!.beacon as! OMCLocationIBeacon

      self.beaconUUID = NSUUID(UUIDString: beacon.uuid!.UUIDString)!
    }
  }
}



The first couple of lines here map against the remote MCS server through a design time construct referred to as a 'mobile backend', then authenticates anonymously.  I'll skip the explanations of these as they are not core to my discussion, they're an MCS implementation detail.

The OMCLocationPlaceQuery instance allows me to build a query against the external MCS API for querying the devices for a specific place, in my case the parent place "Chris's Emporium."  The query call will match any place starting with that name, so in order to get just the first match, I added parameters to limit the result to 1 and sort by ascending on the name.

Next, via the actual async call to executeWithCompletionHandler this will return a 'placesResult' array containing the single OMCLocationPlace representing my Emporium, and then within that I can then retrieve the one OMCLocationeDevice representing the beacon with UUID, which I can then use for my beacon registration I coded earlier.

So remember the purpose of this code is to remove the hardcoding of the UUID and instead dynamically derive it from my beacon registry so it can easily be changed in the future. Just like previously, though, once I've registered the beacon in iOS, I then need to kick off the code to monitor and handle the beacon events.  That code mostly doesn't change.

When the code is up and running and it detects 1 of 100 beacons with the same UUID, the next problem I wanted to overcome is showing the correct message for the specific beacon found.  To do this in the locationManager:inRegion method when it is called for any beacon matching the UUID, I first need to retrieve the rest of the details about the specific beacon, that is its UUID+major+minor values, then use these to query the remote beacon registry to find out what message to show for the specific beacon which identifies the specific shop (ie. "Welcome to Chris's Emporium at Garden City Shopping Plaza").  This is how I rewrote the method:

// Called when device is in beacons signal range and moving
func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
    if !BeaconDelegate.firstBeaconFound && beacons.count > 0 {
        let beacon: CLBeacon = beacons.first!

        if beacon.proximity == CLProximity.Immediate {
            BeaconDelegate.firstBeaconFound = true;

            let mcsMbe: OMCMobileBackend = OMCMobileBackendManager.sharedManager().defaultMobileBackend!
            let loginError: NSError! = mcsMbe.authorization.authenticateAnonymous()
            // TODO: Handle loginError

            let queryDevice: OMCLocationDeviceQuery = mcsMbe.location().buildDeviceQuery()
            queryDevice.beacon = OMCLocationIBeacon(UUID: beacon.proximityUUID, major: beacon.major, minor: beacon.minor)

            queryDevice.executeWithCompletionHandler{ (devicesResult, devicesError) in
                // TODO: handle devicesError

                let beacon: OMCLocationDevice = devicesResult!.items.first as! OMCLocationDevice
                let message: String = beacon.attributeForKey("MyCustomMessage")!

                let alert = UIAlertController(title: "Custom Message", message: message, preferredStyle: UIAlertControllerStyle.Alert)
                alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default,handler: nil))
                UIApplication.sharedApplication().windows[0].rootViewController?.presentViewController(alert, animated: true, completion: nil)
            }
        }
    }
}



In a very similar fashion to the other MCS query code, I can also use the MCS iOS SDK to query from MCS the devices, in this case using the specific UUID+major+minor values of the beacon I just found, which amongst other data returns the custom attribute I defined earlier containing the customer message.  I then show this through the alert.

Image title

Of course, you might not want to stop there, and rather you might want to call an MCS custom API to raise a push notification to the user, or record analytics that the user has reached the beacon location and so on.

Conclusion

Working with most technologies is typically easy once we know about some of the oddities and challenges we'll face up front.  As we saw iOS has limits with the number of regions/beacons we can monitor for, and using a beacon registry like MCS is a good idea to avoid hardcoding the beacon data in our app.  While I'll happily agree my code is 100% robust, I hope with the article in hand it will not only help you on the path to adopting beacons into your own apps, but also identifies some of the research and problem solving along the way to help you understand how I puzzled this out.

Take a deep dive into Bluetooth mesh. Read the tech overview and discover new IoT innovations.

Topics:
mobile ,beacons ,ios ,swift ,location

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}