Tutorial: iBeacon App Development with CoreLocation on Apple iOS 7/8 May 29 2014 130 Comments

UPDATE (7/2/2014): We've just made a couple of minor changes to the tutorial so it now also works with iOS 8. Specifically, you now need to request Always location authorization, which is covered in the steps below. We've also just added a Swift version of this tutorial.

We're going to do a detailed tutorial of how to build an iBeacon app from scratch on Apple iOS 7, complete with background ranging and notifications. However, before getting started building your app or working through the tutorial, you'll need to complete a few prerequisite tasks:

  • Join the Apple iOS developer program
  • Install the latest version of XCode
  • Provision an iOS device for testing

The basic getting started page from Apple covering these and other topics can be found here, but we'll dive into a little more detail of the various steps to help you get up and running as quickly as possible. Then we'll dive in headfirst to our code tutorial, including how to implement ranging while you're app is in the background.

If you need iBeacon hardware, take a look at our selection. We offer two different iBeacon transmitter modules, both with fast (same-day on orders placed before 3pm eastern time), free first-class shipping from Brooklyn, NY to anywhere in the world! If you don't see what you need, drop us a line at sales@ibeaconmodules.us.

Once you have your hardware, be sure to visit the product pages for simple activation instructions before using your beacons for the first time.

If all this is over your head, but you still have an idea for using iBeacon technology, contact our development team for information and pricing on custom application design and development services.

On the other hand, if you are already all set up with an Apple iOS Developer account and a provisioned device, you can skip to the main tutorial.

Let's get started!

 

Join Apple iOS Developer Program

The Apple iOS Developer Program gives you access to the necessary tools to build and test iOS applications on physical devices as well as the ability to distribute your applications on the App Store.

You can find an introduction to the program here. Click the "Enroll Now" button to start the enrollment process. Membership costs $99/year and requires a bit of background information about you (and your business if you're starting a business account). It also can take 24 hours for membership to be approved, so it's a good idea to get started on this step right away, even if you haven't yet received your iBeacon transmitter modules.

Install XCode

With the latest releases of Mac OSX, XCode can be easily installed via the Mac App Store. Just install it, or if you previously installed it, be sure that any updates are installed.

Provision Device

Provisioning your device authorizes it to be used on behalf of your Apple iOS Developer Program account to test the applications you build with XCode.

Apple has a step by step walkthrough in the developer library.

You may also find this video helpful:

 

 

iBeacon Template Application Tutorial

You can download the template project from GitHub, but sometimes it's more illuminating to walk through the steps yourself:

 

1. Create the project

Create a new project in XCode using the "Single View Application" template.

iBeacon Application Creation in Xcode

 

2. Configure the project options

You can name things whatever you like, but here are the settings we used for the template.

iBeacon Xcode Project Options

 

3. Add the CoreLocation framework to the project.

Click on the main project entry in the file browser. Then make sure the General tab is selected. Scroll to the bottom and click the + to add a Linked Framework. Then find CoreLocation.framework in the list, select it, and click the Add button.

Adding CoreLocation Framework to iBeacon Xcode Project

 

4. Configure Background Capabilities

In order to perform background ranging and monitoring and receive notifications, your app needs to register its need to use Location updates and Bluetooth LE accessories in the background. To do this, click the main project entry in the file browser. Then select the Capabilities tab and find Background Modes. Turn the switch ON, and then check Location updates and Uses Bluetooth LE accessories.

Configuring Background Capabilities for iBeacon Project in Xcode

 

5. Define a region to be monitored

For now, we're going to set things up in the -application:didFinishLaunchingWithOptions method in the App Delegate. First, we need to import the header, and then we will set up the UUID for our beacon(s). A UUID is just an identifier that identifies a beacon or set of beacons. Any beacons you buy from us will come with the same UUID. They can be differentiated by another pair of IDs, called major and minor, but we'll get to that at another time. You also need to define an identifier for the region. This will let you distinguish between sets of beacons should your application ever track more than one. For now, the top part of your App Delegate implementation should look like this:

#import "IMAppDelegate.h"
#import <CoreLocation/CoreLocation.h> @implementation IMAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions { // Override point for customization after application launch. NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString: @"EBEFD083-70A2-47C8-9837-E7B5634DF524"]; NSString *beaconIdentifier = @"iBeaconModules.us"; CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID: beaconUUID identifier:beaconIdentifier]; return YES; }

 

6. Set up plist entry for LocationAlways

iOS 8 requires special permission for always-on location monitoring, which includes iBeacon ranging. You need to include a specific plist entry that enables iOS to tell the user why you want this permission.

In the file browser under Supporting Files, open Info.plist. Select the top row "Information Property List". Click the small plus sign to Add a Row. Give it the key (in the left column) NSLocationAlwaysUsageDescription. Make sure the type is set to String, and then set its value to whatever string you'd like to use to inform the user when they are prompted for location permissions.

7. Set up CLLocationManager for foreground/background monitoring and ranging

Now we have a region defined, but it won't do anything until we tell the device to monitor it. To do this, we need to create an instance of CLLocationManager and tell it to start monitoring the region we just created. We also need to define a "delegate" which is an object that gets notified whenever something happens with the location manager. We'll just use the App Delegate for now.

We need to modify the .h file for the App Delegate to do a few things. First, we need to import CoreLocation headers. Then we need to declare that the class implements the CLLocationManagerDelegate protocol, and declare a property to hold our CLLocationManager object so that it survives for the entire lifetime of the application. After this, the header (.h) file should look like this:

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>

@interface IMAppDelegate : UIResponder <UIApplicationDelegate, 
  CLLocationManagerDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) CLLocationManager *locationManager;

@end 

Then, back in the implementation (.m) file, we need to create the CLLocationManager instance and tell it to start monitoring the region, start ranging the beacons, and start updating location. All three of these are required to receive range information updates when the app is in the background. After this, -application:didFinishLaunchingWithOptions: should look like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
  (NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString:
       @"EBEFD083-70A2-47C8-9837-E7B5634DF524"];
    NSString *regionIdentifier = @"us.iBeaconModules";
    CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] 
      initWithProximityUUID:beaconUUID identifier:regionIdentifier];

    self.locationManager = [[CLLocationManager alloc] init];
    // New iOS 8 request for Always Authorization, required for iBeacons to work!
    if([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
        [self.locationManager requestAlwaysAuthorization];
    }
    self.locationManager.delegate = self;
    self.locationManager.pausesLocationUpdatesAutomatically = NO;    
[self.locationManager startMonitoringForRegion:beaconRegion]; [self.locationManager startRangingBeaconsInRegion:beaconRegion]; [self.locationManager startUpdatingLocation]; return YES; }

8. Define what happens when the region is entered

The device is now monitoring the beacon, but we still haven't told it what to do when it sees it. Because we set our App Delegate as the CLLocationManager instance's delegate back in the previous step, it's going to look for methods here to call when various events happen. You can find a list of methods in the CLLocationManagerDelegate Protocol Reference.

-locationManager:didRangeBeacons:inRegion: looks interesting. Let's tell it to do something. Add the following methods to your App Delegate implementation:

-(void)sendLocalNotificationWithMessage:(NSString*)message {
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    notification.alertBody = message;
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
}

-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:
   (NSArray *)beacons inRegion:(CLBeaconRegion *)region {
    NSString *message = @"";

    if(beacons.count > 0) {
        CLBeacon *nearestBeacon = beacons.firstObject;
        switch(nearestBeacon.proximity) {
            case CLProximityFar:
                message = @"You are far away from the beacon";
                break;
            case CLProximityNear:
                message = @"You are near the beacon";
                break;
            case CLProximityImmediate:
                message = @"You are in the immediate proximity of the beacon";
                break;
            case CLProximityUnknown:
return; } } else { message = @"No beacons are nearby"; } NSLog(@"%@", message); [self sendLocalNotificationWithMessage:message]; }

When a beacon range is updated, this code looks to see if beacons were found. If there is at least one, the first one is chosen, and depending upon its proximity a message is logged and a local notification is scheduled. The notification will appear once per second when the application is running in the background. 

 

9. Clean up

This will work as is, but let's tidy things up just a bit. Since we've implemented background ranging with background notifications, you'll get a lot of redundant alerts (one per second while the app is in the background), so we should modify things to only alert us when the range changes. First, we need to add a place to store the last proximity reading. Add the following property to the header (.h) file of the App Delegate:

@property CLProximity lastProximity;

Then, back in the implementation (.m) file, add this before the switch statement that handles the proximity reading:

        if(nearestBeacon.proximity == self.lastProximity || 
nearestBeacon.proximity == CLProximityUnknown) { return; } self.lastProximity = nearestBeacon.proximity;

If the proximity hasn't changed or is temporarily unknown, the method returns and doesn't re-alert us.

That's great, but now what happens if we leave the beacon range entirely? The device will keep trying to range beacons that are no longer in range which just wastes power. Luckily, the CLLocationManager will tell us via a different method when we have entered and exit the range, and we can start and stop the more detailed ranging operations accordingly. Add these methods to the App Delegate implementation:

-(void)locationManager:(CLLocationManager *)manager 
    didEnterRegion:(CLRegion *)region {
    [manager startRangingBeaconsInRegion:(CLBeaconRegion*)region];
    [self.locationManager startUpdatingLocation];
    
    NSLog(@"You entered the region.");
    [self sendLocalNotificationWithMessage:@"You entered the region."];
}

-(void)locationManager:(CLLocationManager *)manager 
    didExitRegion:(CLRegion *)region {
    [manager stopRangingBeaconsInRegion:(CLBeaconRegion*)region];
    [self.locationManager stopUpdatingLocation];
    
    NSLog(@"You exited the region.");
    [self sendLocalNotificationWithMessage:@"You exited the region."];
}

 This also logs and alerts us of the system-level region entry/exit.

 

10. Foreground Display

OK, things are great in background mode, but when the app is foregrounded, all we have is a plain white screen and no indication of what's happening. Let's fix that by adding a UITableView and populating it with information about all of the beacons in range.

Open the Main.storyboard file. Then select the main view to give it focus. Then, in the list in the bottom right (you may need to use the buttons in the top right corner of the screen to open the tray on the right side of the window), find a Table View. Drag it onto the view and position and scale it so that it takes up the entire view except the area taken up by the status bar at the top.

Adding a UITableView to the iBeacon Project in Xcode

 

When you're done, it should look like this:

After Adding the UITableView to the iBeacon Project in Xcode

 

Now open up your View Controller header file (.h) (NOTE: this is NOT the same file you were editing before). In there, we need to declare a property to reference the table view we just created:

@property IBOutlet UITableView *tableView;

Next, open the storyboard file again. Then option-click (or right-click) on the table view you just added. In the context menu that pops up, drag from the little circle to the right of dataSource over to the View Controller entry in the left portion of the storyboard view:

Connecting the UITableViewDelegate in the iBeacon Project in Xcode

Do the same for delegate and New Referencing Outlet. When you drop the connection for New Referencing Outlet, you'll get a pop-up asking which variable the table view should map to. Select tableView, which matches the name of the property we just created.

Now we need to declare our View Controller to implement the protocols required by the above connections, UITableViewDataSource and UITableViewDelegate. Add them to the declaration of your view controller in the header file, like this:

@interface IMViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>

Create a beacons NSArray property in the view controller header file:

@property (strong) NSArray *beacons;

When the beacons are ranged in the app delegate, now we need to pass the array of beacons to the View Controller and tell the table view to update itself. Add the following code to the top of locationManager:didRangeBeacons:inRegion in the App Delegate:

    IMViewController *viewController = 
(IMViewController*)self.window.rootViewController; viewController.beacons = beacons; [viewController.tableView reloadData];

Now we just need to implement -tableView:numberOfRowsInSection: and -tableView:cellForRowAtIndexPath: in our View Controller. These methods are called by the table view so it can update itself with the latest data:

- (NSInteger) tableView:(UITableView *)tableView 
numberOfRowsInSection:(NSInteger)section { return self.beacons.count; } - (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:@"MyIdentifier"]; if (cell == nil) { cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:@"MyIdentifier"]; cell.selectionStyle = UITableViewCellSelectionStyleNone; } CLBeacon *beacon = (CLBeacon*)[self.beacons
objectAtIndex:indexPath.row]; NSString *proximityLabel = @""; switch (beacon.proximity) { case CLProximityFar: proximityLabel = @"Far"; break; case CLProximityNear: proximityLabel = @"Near"; break; case CLProximityImmediate: proximityLabel = @"Immediate"; break; case CLProximityUnknown: proximityLabel = @"Unknown"; break; } cell.textLabel.text = proximityLabel; NSString *detailLabel = [NSString
stringWithFormat:
@"Major: %d, Minor: %d, RSSI: %d, UUID: %@", beacon.major.intValue, beacon.minor.intValue,
(int)beacon.rssi, beacon.proximityUUID.UUIDString]; cell.detailTextLabel.text = detailLabel; return cell; }

There's a very good explanation of the table view functionality in this Stack Overflow answer.

 

Conclusion

 iBeacon Background Notification on iOS Lock Screen iBeacon Ranging Display using a UITableView

Now we've built an iBeacon app that can range beacons in the foreground and background and display the data and push alerts, so we're going to stop there for now. You can also download the entire project from GitHub if you'd rather not build it up piece by piece. I hope this helps you get up and running and hopefully understand a bit more about iBeacon and how it's used with iOS7. If you have questions or feedback on this tutorial, leave us a comment!

If you need iBeacon hardware, take a look at our catalog. We offer a range of iBeacon transmitter modules, all with fast, free domestic shipping within the U.S. If you don't see what you need, drop us a line at sales@ibeaconmodules.us.