May 11, 2016 A Beacon Development Tutorial for iOS Learn how to develop a beacon ranging app in Swift and Objective-C compatible with iBeacon technology to learn more about or better engage users. Gavin Finden Beacon ranging can be used to convey more than proximity to a given location. There are a myriad of opportunities to employ beacon development to learn more about or better engage users. Picture this. Before conference attendees enter an expo or trade show hall, they download an app that uses beacon ranging technology. With that app running for the duration of the time they are in the expo hall, they are delivered information relevant to nearby exhibitors as they approach strategically placed beacons throughout the experience. Or, maybe a museum or art gallery system designed an app leveraging beacon technology to deliver information about the featured artists or exhibit. As users walk through the space nearing proximate beacons, the send is triggered augmenting the physical experience with the virtual. This same approach is even being used to deliver consumers related coupons as they navigate a retail space. You can also create a network of beacons to simultaneously perform indoor location tracking, where GPS signals don’t penetrate very well. This can give you more accurate positioning than the simple proximity tracking that a single beacon offers and could potentially be used to show a user’s location on an indoor map of a mall or other space. Used in healthcare settings they could give insight into how industry professionals are spending their time — and where — to identify and reduce any existing inefficiencies. Want to know more about developing health IT? iOS beacon tutorial In this iOS beacon tutorial, we will explore how to build both a Swift and Objective-C implementation of a beacon ranging app. Looking at both languages, we can compare how they fare in accomplishing the same task. When we setup development for beacon ranging, we want to measure the Received Signal Strength Indicator (RSSI) value, which captures the strength of the signal received. As a user’s proximity to a beacon decreases, the RSSI value will decrease, and as the user gets closer the value will increase. By measuring the RSSI values, we can attempt to estimate the proximity of a beacon to our current location. We can also use the values from several beacons at once to determine which beacons we’re closer to relative to the others. Developing a beacon ranging application Beacon Ranger is a sample app that simply performs beacon ranging on nearby Estimote and Kontakt beacons (and can easily be modified to range beacons from other manufacturers as well). The finished product includes both a Swift and Objective-C implementation. Download the starter project. Open the project in `Xcode` to start building. iOS beacon example project overview Initially we have just a basic `UITableView` that is displayed with two rows. Both go to two other empty `UITableView`s, so there’s not too much to see yet. In the project you’ll find two classes: `SwiftBeaconListTableViewController` and `ObjCBeaconListTableViewController`. These classes will contain the respective implementations in Swift and Objective-C to perform beacon ranging. We will fill in the implementations for these methods later on. Most of the app at this point is in the storyboard. Open `Main.storyboard`. Start by taking a look around. You’ll see that it’s pretty simple. The initial `UITableViewController` that is displayed doesn’t even need a custom class. It’s handy when as much view and layout-related information can be placed in a storyboard, so the code you’re building can define the logic of the app. Implementing the beacon ranging app Getting permission for the user’s location Beacon monitoring and ranging is part of the `CoreLocation` framework, and access to location services requires permission from the user. Open `Info.plist`, and add the key `NSLocationWhenInUseUsageDescription` with String value `Required to monitor beacons`. The value specified here is the reason that will be shown to the user why your app needs access to location services. Accessing the `CoreLocation` framework Now we need to import the `CoreLocation` framework so that we can use it. Add the following right below the import statement near the top of the file: Swift import CoreLocation Objective-C @import CoreLocation; `CLLocationManagerDelegate` We receive beacon ranging updates on iOS by implementing the `CLLocationManagerDelegate` protocol. We are making our controller class conform to the `CLLocationManagerDelegate` protocol so we can get updates on the range of beacons. In the Swift version, we are doing this in a class extension where we can also place the `CLLocationManagerDelegate` methods that we implement, leading to cleaner looking code where the delegate methods are separated out. Swift Add the following at the end of the file after the `// MARK: - Location Manager Delegate` comment: extension SwiftBeaconListTableViewController: CLLocationManagerDelegate { } Objective-C Near the top of the file, find the following line: @interface ObjCBeaconListTableViewController () and change it to be: @interface ObjCBeaconListTableViewController () <CLLocationManagerDelegate> Start Ranging Next, we need to add a property to hold onto a `CLLocationManager` for use while we’re doing beacon ranging. Swift Add the following line to the top of the class: var manager: CLLocationManager? Objective-C Add the following line to the class extension: @property (nonatomic, strong) CLLocationManager *manager; Now we need to start performing beacon ranging when we navigate to our beacon list screen. Add the following in `viewDidAppear` below the `super` call to `viewDidAppear`: Swift // 1 if CLLocationManager.isRangingAvailable() { // 2 manager = CLLocationManager() manager!.delegate = self // 3 manager!.requestWhenInUseAuthorization() // 4 let beaconRegions = [CLBeaconRegion(proximityUUID: NSUUID(UUIDString: "F7826DA6-4FA2-4E98-8024-BC5B71E0893E")!, identifier: "Kontakt"), CLBeaconRegion(proximityUUID: NSUUID(UUIDString: "B9407F30-F5F8-466E-AFF9-25556B57FE6D")!, identifier: "Estimote")] // 5 beaconRegions.forEach(manager!.startRangingBeaconsInRegion) } else { // 6 let alert = UIAlertController(title: "Unsupported", message: "Beacon ranging unavailable on this device.", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "Ok", style: .Default) { alertAction -> Void in self.navigationController?.popViewControllerAnimated(true) }) self.presentViewController(alert, animated: true, completion: nil) } Objective-C // 1 if ([CLLocationManager isRangingAvailable]) { // 2 self.manager = [CLLocationManager new]; self.manager.delegate = self; // 3 [self.manager requestWhenInUseAuthorization]; // 4 NSArray<CLBeaconRegion *> *beaconRegions = @[[[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:@"F7826DA6-4FA2-4E98-8024-BC5B71E0893E"] identifier:@"Kontakt"], [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:@"B9407F30-F5F8-466E-AFF9-25556B57FE6D"] identifier:@"Estimote"]]; // 5 for (CLBeaconRegion *region in beaconRegions) { [self.manager startRangingBeaconsInRegion:region]; } } else { // 6 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Unsupported" message:@"Beacon ranging unavailable on this device." preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self.navigationController popViewControllerAnimated:YES]; }]]; [self presentViewController:alert animated:YES completion:nil]; } Let’s look at what’s going on here: 1. First we need to check if beacon ranging is available on our device. If it is an older device without Bluetooth LE support or we’re running in the iOS Simulator, beacon ranging will be unavailable. 2. If beacon ranging is available, we want to create a new `CLLocationManager` that we hold onto, and make ourselves the delegate of it. 3. We’ll only be doing beacon ranging while the app is in the foreground, so we’re only requesting location permission while we’re using the app and not in the background. This is what will make the app actually prompt the user to use their location. 4. We will create some beacon regions to do ranging in. Here, we’re using standard UUIDs for Estimote and Kontakt brand beacons, but you can add additional UUIDs or replace these entirely with different ones corresponding to your own beacons. 5. Now we will start performing ranging of beacons in these regions. Notice the very compact Swift code by using `forEach` and taking advantage of the fact that the method we want to call is already the right signature to pass directly to `forEach` so that we don’t even have to use a closure. The Objective-C code isn’t as elegant. 6. If ranging was unavailable, we let the user know and we go back. Make sure you’re testing on a device. When we navigate away, we want to stop beacon ranging so we’re not draining the battery when the results aren’t being stored. Add the following in `viewWillDisappear` below the `super` call to `viewWillDisappear`: Swift if let rangedRegions = manager?.rangedRegions as? Set<CLBeaconRegion> { rangedRegions.forEach(manager!.stopRangingBeaconsInRegion) } Objective-C if (self.manager) { for (CLBeaconRegion *region in self.manager.rangedRegions) { [self.manager stopRangingBeaconsInRegion:region]; } } You can see that this code is similar when we started performing beacon ranging, except that we’re getting the list of beacon regions from our location manager, so we don’t have to even reference our original list. You can launch the app and navigate to the beacon list screen now, and it will start performing beacon ranging. You should see the location icon appear in the status bar at the top of the screen to indicate that location services is in use, and it will disappear a short time after you navigate away. So far, we’re not doing anything with that data. Curious about prioritizing beacon development? Save the results First we need to keep track of the list of beacons we’ve seen and which region they’re in. Swift Add the following below where the `CLLocationManager` is declared: var beaconList = [(CLBeaconRegion, CLBeacon)]() Objective-C Add the following below where the `CLLocationManager` is declared: @property (nonatomic, strong) NSMutableArray<CLBeacon *> *beaconList; @property (nonatomic, strong) NSMutableDictionary<CLBeacon *, CLBeaconRegion *> *regionForBeacon; Then add the following in `viewDidLoad` below the `super` call to `viewDidLoad`: self.beaconList = [NSMutableArray array]; self.regionForBeacon = [NSMutableDictionary dictionary]; You can see that in the Swift code, we’re using a tuple, which allows us to store all of the beacons in an array with their associated region with ease. We don’t have tuples in Objective-C, however, so we need a different solution. Here, we’re going to use a separate `NSDictionary` to map from the `CLBeacon` in our list to the appropriate `CLBeaconRegion`. The other difference between these two is that we can initialize the Swift Array in place where we declare it, but we have to do it somewhere else in the Objective-C implementation. Here we’re doing that in `viewDidLoad`. Now that we have a place to store the beacon data, we need to actually start storing it. Swift Add the following inside the class extension at the bottom of the file: func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) { // 1 var outputText = "Ranged beacons count: \(beacons.count)\n\n" beacons.forEach { beacon in outputText += beacon.description.substringFromIndex(beacon.description.rangeOfString("major:")!.startIndex) outputText += "\n\n" } NSLog("%@", outputText) // 2 beacons.forEach { beacon in if let index = beaconList.indexOf({ $0.1.proximityUUID.UUIDString == beacon.proximityUUID.UUIDString && $0.1.major == beacon.major && $0.1.minor == beacon.minor }) { beaconList[index] = (region, beacon) } else { beaconList.append((region, beacon)) } } // 3 tableView.reloadData() } Objective-C Add the following after the line `#pragma mark - Location Manager Delegate`: - (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray<CLBeacon *> *)beacons inRegion:(CLBeaconRegion *)region { // 1 NSMutableString *outputText = [NSMutableString stringWithFormat:@"Ranged beacons count: %lu\n\n", (unsigned long)beacons.count]; for (CLBeacon *beacon in beacons) { [outputText appendString:[beacon.description substringFromIndex:[beacon.description rangeOfString:@"major:"].location]]; [outputText appendString:@"\n\n"]; } NSLog(@"%@", outputText); // 2 for (CLBeacon *beacon in beacons) { NSUInteger index = [self.beaconList indexOfObjectPassingTest:^BOOL(CLBeacon * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { BOOL match = [obj.proximityUUID.UUIDString isEqualToString:beacon.proximityUUID.UUIDString] && (obj.major.integerValue == beacon.major.integerValue) && (obj.minor.integerValue == beacon.minor.integerValue); if (match) { *stop = YES; } return match; }]; if (index != NSNotFound) { self.regionForBeacon[self.beaconList[index]] = nil; self.beaconList[index] = beacon; self.regionForBeacon[beacon] = region; } else { [self.beaconList addObject:beacon]; self.regionForBeacon[beacon] = region; } } // 3 [self.tableView reloadData]; } Let’s take a closer look at this: 1. Here we’re printing out to the console the list of beacons that we’re detecting. This part isn’t strictly necessary and would likely be removed in a production app, but it’d a useful debugging tool in case something isn’t working the way you expect it to. 2. Here’s where we add the newly ranged beacons to the list, but we don’t want duplicates, so we have to check first to see if we already added the beacon. The Swift code is much simpler here, due to the cleanness and compactness of Swift syntax and our use of a tuple to store the beacon list. If we find the beacon already in the list, we simply update it with the new one, because each `CLBeacon` contains the RSSI value that we want to display. Each time we get ranging updates, the RSSI values might change. The Objective-C code is more complex due to the fact that the Objective-C syntax to find the index requires more code, and also because we need to keep track of the beacon to region mapping in a separate `NSDictionary`. 3. Reload the table, because we have new data. Launch the app and go to the beacon list screen. Beacon ranging will start, and the data will get stored. Sound right? But wait, we don’t see anything in our table. That’s because we still have yet to implement our table view data source methods to show the results. Update the UI First we need to tell the table view how many rows it should be displaying. Under the table view data source part of the file, change the body of the `numberOfRowsInSection` method to the following: Swift return beaconList.count Objective-C return self.beaconList.count; Then we need to tell the `UITableView` exactly what data to display in each row. Replace `// Configure the cell...` comment in `cellForRowAtIndexPath` with the following: Swift let (region, beacon) = beaconList[indexPath.row] cell.textLabel?.text = "\(region.identifier), Major: \(beacon.major), Minor: \(beacon.minor)" cell.detailTextLabel?.text = "\(beacon.rssi)" Objective-C CLBeacon *beacon = self.beaconList[indexPath.row]; CLBeaconRegion *region = self.regionForBeacon[beacon]; cell.textLabel.text = [NSString stringWithFormat:@"%@, Major: %@, Minor: %@", region.identifier, beacon.major, beacon.minor]; cell.detailTextLabel.text = [NSString stringWithFormat:@"%li", (long)beacon.rssi]; Now when we run the app, we’ll see our list of beacons displayed, and the RSSI values will update as the beacon signal strength varies. You can see that these will fluctuate even if your phone and your beacons aren’t being moved at all, and occasionally you’ll get RSSI values of 0, which means that it didn’t receive any readings for that beacon since the last update. Final notes Certain pieces of our iOS beacon tutorial were more concise in Swift than they were in Objective-C, which can lead to overall simpler code that is easier to understand. Swift’s functional programming constructs, as well as Swift’s new data structures such as tuples, allow us to write more expressive code and in fewer lines. Download the starter project. Tags MobileDevelopment Share Share on Facebook Share on LinkedIn Share on Twitter Share Share on Facebook Share on LinkedIn Share on Twitter Sign up for our monthly newsletter. Sign up for our monthly newsletter.