iBeacons Tutorial for iOS 7 with CLBeaconRegion and CLBeacon

来源:互联网 发布:新东方烹饪 知乎 编辑:程序博客网 时间:2024/06/08 08:57

At WWDC earlier this year (2013) Apple announced iOS 7. Along with that announcement we heard of new API called iBeacons. iBeacons is a technology that uses transmitters and receivers which can let your iOS device know how far you are away from a beacon. One device acts as the transmitter and the other is a receiver.

iBeacon transmitters can either be a dedicated hardware device running Bluetooth 4.0 LE or you can configure a compatible iPhone or iPad to act as a transmitter. The receiving device is a compatible iPhone or iPad that can receive Bluetooth 4.0 LE. Compatible devices include the iPhone 4S and newer and the iPad 3 and newer (including iPad mini).

To give an example of what you could use iBeacons for, think of a group of museums managed by a single company. Each room in each of the museums would have an iBeacon transmitter placed in them. Each beacon is configured with settings or what Apple refers to as an identity. If you are running an app provided by the museum, each time you enter the museum you will be presented with information which changes depending on what room you are in.

There are three settings that set a transmitters identity:

proximityUUID is a property which is unique to each company. In the case of the museum company,the same UUID would be given to all beacons. You generate a UUID by loading up the terminal on a Mac and entering uuidgen. It provides a UUID such as “23542266-18D1-4FE4-B4A1-23F8195B9D39″.

major is the property that you use to specify a related set of beacons. This is a numeric value such as 1 could mean the museum in London, England and 2 could mean the museum in Leeds, England. All beacons in a location are assigned the correct value for major according to the location… so all beacons in Leeds would have the major property set to 2.

minor in this case would be used to specify a particular beacon in a museum. In our example, we could use minor to set the room number in the building.

When an iPhone is running an app (made by the museum) it can be programmed to look for the museums UUID. When it detects it, it looks at the major and minor values and can determine which of the museums you are in as well as the room the you are in.

So when the app detects you have entered the region (AKA, picked up a transmitter) containing the museum UUID of “21541166-18D1-4FE4-B4A1-26F8135B3D31″ and a major of 1 and minor of 14, it knows you are in the London museum and in room 14 of that museum. The app could then provide extra information such as video presentations at the art work in the room, or perhaps show you extra information in video/audio or text form about a particular piece.

There are many ways you can work with iBeacons. Indoor navigation could be one possibility. Think of a busy airport kitted out with hundreds of beacons each telling you exactly where you are and need to go to get to your gate. Think of shops that could put them around the shelves so you can more easily find products. Think of the abilities that iBeacons have to let you know you are within very close proximity of a transmitter (similar to RFID) so that doors could be unlocked or payments could be processed.

With this in mind, I want to do a quick tutorial showing how you can set up an iOS device as a transmitter and set up another as a receiver. Note that the requirements are:

1. You must be enrolled in the iOS developer program ($99/year). This tutorial requires you to run the test app on two iOS devices.
2. You must have 2 iOS devices which have Bluetooth 4.0. This includes the iPhone 4S and newer, iPad 3 and newer and the iPad mini.
3. You need a Mac of course, running Xcode.

You can check out our iOS tutorials on this site, DefFright.

iBeacons Tutorial

The first part of this tutorial will have us set up a single view application. When done, set up the storyboard similar to what you see below.

iBeacon

The first View Controller can be embedded in a Navigation Controller by clicking on it, clicking Editor > Embed In > Navigation Controller.

I put two buttons in there. One called Track Beacon and the other Transmit Beacon. I dragged out two more view controllers (top right and bottom right) and CRTL+dragged from each button to one of the view controllers and configured them as a push segue.

For the Track View Controller and Config View Controller, I created a custom class for each andassigned them to the view controllers.

When this is set up, drag all the UILabels and buttons in place.

For the Track View Controller, I CTRL+Dragged all the labels on the right to the following IBOutlets:

@property (weak, nonatomic) IBOutlet UILabel *beaconFoundLabel;@property (weak, nonatomic) IBOutlet UILabel *proximityUUIDLabel;@property (weak, nonatomic) IBOutlet UILabel *majorLabel;@property (weak, nonatomic) IBOutlet UILabel *minorLabel;@property (weak, nonatomic) IBOutlet UILabel *accuracyLabel;@property (weak, nonatomic) IBOutlet UILabel *distanceLabel;@property (weak, nonatomic) IBOutlet UILabel *rssiLabel;
For the Config View Controller, I added the following IBOutlets in the same way:

@property (weak, nonatomic) IBOutlet UILabel *uuidLabel;@property (weak, nonatomic) IBOutlet UILabel *majorLabel;@property (weak, nonatomic) IBOutlet UILabel *minorLabel;@property (weak, nonatomic) IBOutlet UILabel *identityLabel;

Configuring the Main View Controller

As we only do a push segue from buttons to other view controllers, the ViewController.h and .m files do not need to be modified.

Setting up an iBeacon Transmitter

For this part of the tutorial, we are going to focus on setting up an iOS device as a transmitter.

To set up a transmitter we need the following frameworks importing:

frameworksCoreBluetooth.framework and CoreLocation.framework. Add these in the linked frameworks and libraries section.

After these have both been imported, we then need to import each of them in to our ConfigViewController header file:

#import <CoreLocation/CoreLocation.h>#import <CoreBluetooth/CoreBluetooth.h>
Next, we need to add some properties to work with:

@property (strong, nonatomic) CLBeaconRegion *beaconRegion;@property (strong, nonatomic) NSDictionary *beaconPeripheralData;@property (strong, nonatomic) CBPeripheralManager *peripheralManager;

Line 1 above, we create a CLBeaconRegion property called beaconRegion. This property is used to define the settings (proximityUUID, major and minor) that are needed to set up a beacon as a transmitter.

Line 2 we create an NSDictionary property which contains the peripheral data of the beacon.

Line 3, we create the CBPeripheralManager property where the methods are contained to start it transmitting.

Switching over to the implementation file, there are several things we need to do to get an iOS device transmitting a signal.

In this tutorial, I am using 4 methods as well as the viewDidLoad method to get the transmitter working. viewDidLoad contains method calls to initBeacon and setLabels. initBeacon is used to configure the beacon. Lets look at that first.

- (void)initBeacon {    NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:@"23542266-18D1-4FE4-B4A1-23F8195B9D39"];    self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid                                                                major:1                                                                minor:1                                                           identifier:@"com.devfright.myRegion"];}

On line 2 we set up an NSUUID by initialising it with an NSString. In this case, I opened up the terminal on my mac and typed in uuidgen and then pasted the provided UUID in the initialiser. You can generate UUIDs from code if you wish, but it isn’t really necessary in this case.

Line 3 we alloc and init the self.beaconRegion property and call the initWithProximityUUID:major:minor:identifier: method. In this case, I hard coded the settings other than UUID which was pulled in from an NSUUID object. I just assigned 1 for major, 1 for minor and an identifier string of com.devfright.myRegion.

Moving on, we now have a transmitBeacon method which is an IBAction of a button on the view controller on the storyboard. The method looks like this:

- (IBAction)transmitBeacon:(UIButton *)sender {    self.beaconPeripheralData = [self.beaconRegion peripheralDataWithMeasuredPower:nil];    self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self                                                                                     queue:nil                                                                                   options:nil];}

On line 2 we call peripheralDataWithMeasuredPower on the self.beaconRegion property. This method returns an NSDictionary. We assign the result to our NSDictionary property which is called beaconPeripheralData.

On line 3 we alloc/init the peripheralManager property. Here we are instructed by Apple to call initWithDelegate:queue:options. We just provide nil and nil for the queue and options and set the delegate to self.

When we set the delegate to self in that initialiser, we will get a warning. To fix that, we need to make sure the class conforms to the CBPeripheralManagerDelegate protocol. Add the delegate on to the end of the @interface line in the header as below:

@interface ConfigViewController : UIViewController <CBPeripheralManagerDelegate>
When we adopt this protocol, we are then given another warning which requires we implement a particular method from the delegate. Lets look at that now:

-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {    if (peripheral.state == CBPeripheralManagerStatePoweredOn) {        NSLog(@"Powered On");        [self.peripheralManager startAdvertising:self.beaconPeripheralData];    } else if (peripheral.state == CBPeripheralManagerStatePoweredOff) {        NSLog(@"Powered Off");        [self.peripheralManager stopAdvertising];    }}

The required method is peripheralManagerDidUpdateState:. I implemented it with the code listed above.

To power on the transmitter, we need to call the startAdvertising: method. If we do this in the transmitBeacon method above, it will likely fail because the Bluetooth services will not be active in time for it to be called and you will get a message logged saying “CBPeripheralManager is not powered on”. Instead, we put that code in the peripheralManagerDidUpdateState method. As soon as the Bluetooth status changes, the delegate calls this method and we can then check to see if it powered on and if so, startAdvertising the beacon.

Line 2 above checks the state and if it is powered on CBPeripheralManagerStatePoweredOn, we can start advertising with the NSDictionary that we created earlier. If not, we can stop Advertising or just do nothing.

Finally, we set the labels to show us what is being transmitted. I call this method from the viewDidLoad method:

- (void)setLabels {    self.uuidLabel.text = self.beaconRegion.proximityUUID.UUIDString;    self.majorLabel.text = [NSString stringWithFormat:@"%@", self.beaconRegion.major];    self.minorLabel.text = [NSString stringWithFormat:@"%@", self.beaconRegion.minor];    self.identityLabel.text = self.beaconRegion.identifier;}

Here, we are simply setting the text property of each of the labels on the view to show the details of what the beacon has been configured to be. Note that the proximityUUID has another property called UUIDString which can be accessed to set the uuidLabel.text property. The identifier is a string also, which means we can assign it as it is. The only different things we need to do is convert major and minor to string with the stringWithFormat class method.

Our app is now capable of transmitting a beacon although its kind of boring if we leave it there. In the next part, we are going to look at how our other iOS device can be set up to be made aware when it is in the vicinity of the transmitter.

Receiving iBeacon Information

Detecting iBeacons is done through the Core Location Framework. The updated framework for iOS 7 can now recognise when you enter the region of a beacon and when doing so, you can then range the beacon to find out information about it.

Lets begin here with the TrackViewController header file. We first need to import the CoreLocation framework:

#import <CoreLocation/CoreLocation.h>
We then need to add some properties as follows:

@property (strong, nonatomic) CLBeaconRegion *beaconRegion;@property (strong, nonatomic) CLLocationManager *locationManager;

Line 1 is a CLBeaconRegion property which is used to define which beacons we are looking for. ie, going back to the museum example we would only want to look for beacons which have the exact same UUID.

Line 2 we add a locationManager property which is used for setting up location services and allowing the beacons to be found.

In the viewDidLoad method we add the following:

self.locationManager = [[CLLocationManager alloc] init];self.locationManager.delegate = self;[self initRegion];
Line 1 we alloc/init the locationManager and line 2, we set delegate to self for the locationManager. This of course requires that we adopt the CLLocationManagerDelegate protocol. Add this to the interface line to your header:

@interface TrackViewController : UIViewController <CLLocationManagerDelegate>

For more information about this delegate protocol, check out our CLLocationManagerDelegatetutorial.

Back to the viewDidLoad method, we then call a method I created called initRegion.

Lets now look at initRegion.

- (void)initRegion {    NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:@"23542266-18D1-4FE4-B4A1-23F8195B9D39"];    self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:@"com.devfright.myRegion"];    [self.locationManager startMonitoringForRegion:self.beaconRegion];}

Line 2 we create an NSUUID object and alloc/init with a string. This string should match the one you created for the transmitter for this one to work.

On line 3 we alloc/init the self.beaconRegion property. The initialiser we use is initWithProximityUUID:identifier and we specify our UUID and our identifier. In this case, I just hard coded the identifier in (copied and pasted the NSString from the transmitter.

We then need to start monitoring this particular beaconRegion. We do that on line 4.

Moving on, we now need to configure the app to detect when it enters or exits a particular beacon region. We do that with the following two methods from the delegate:

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {    [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];} -(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {    [self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];    self.beaconFoundLabel.text = @"No";}

The first method is the didEnterRegion: method. When you start picking up the transmitter (ie, you are close enough to it), this method is called. What we do here is then call the startRangingBeaconsInRegion: method on locationManager. We specify the beacon region that we created earlier in this class (self.beaconRegion).

On the second method, we do exactly the same bit we tell the app to stopRangingBeaconsInRegion: instead.

Lets take a look at the didRangeBeacons method.

-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {    CLBeacon *beacon = [[CLBeacon alloc] init];    beacon = [beacons lastObject];     self.beaconFoundLabel.text = @"Yes";    self.proximityUUIDLabel.text = beacon.proximityUUID.UUIDString;    self.majorLabel.text = [NSString stringWithFormat:@"%@", beacon.major];    self.minorLabel.text = [NSString stringWithFormat:@"%@", beacon.minor];    self.accuracyLabel.text = [NSString stringWithFormat:@"%f", beacon.accuracy];    if (beacon.proximity == CLProximityUnknown) {        self.distanceLabel.text = @"Unknown Proximity";    } else if (beacon.proximity == CLProximityImmediate) {        self.distanceLabel.text = @"Immediate";    } else if (beacon.proximity == CLProximityNear) {        self.distanceLabel.text = @"Near";    } else if (beacon.proximity == CLProximityFar) {        self.distanceLabel.text = @"Far";    }    self.rssiLabel.text = [NSString stringWithFormat:@"%i", beacon.rssi];}

On line 2, we create a CLBeacon object called beacon. We alloc/init it and then on the next line, we pull the last object from the argument in the method (beacons) and store that in beacon. We now have a beacon to work with.

The next few lines we are simply pulling information from the beacon such as its UUIDString, major and minor values, accuracy and RSSI. These will change depending on what the transmitter is sending. If you modify the transmitter code to set minor to 2, this CLBeacon will contain a 2 for minor and so on.

The accuracy, proximity and RSSI are all properties of the beacon which help us determine distance from the beacon. The accuracy will constantly change depending on interference. The proximity provides 4 values. Unknown, immediate, near, far with immediate being about half a meter away at most. Near is a little further away and far is several meters away.

RSSI is the signal strength in decibels.

Running the App

The app is now ready to run. You need to run it on two compatible devices and set one up to transmit and the other one to receive.

What you will notice is that you need to walk a distance away (10 – 20 meters) from the transmitter and then walk back towards it for this to work. When you “enter the region” the didRangeBeacons is called by the didEnterRegion method and the CLBeacon is pulled from the provided beacons NSArray and the information is then put to the view on the screen.

Note that we are only pulling the lastObject from the beacons NSArray. The reason for this is that we are only testing with a single beacon. If we were to have multiple beacons, we would want to query all beacons in the NSArray to figure out where we are in proximity to each one.

Testing Without Entering or Exiting the Region

If you don’t want to leave your home briefly each time you want to test, there’s a quick (and shoddy) work around. Add the following to viewDidLoad:

[self locationManager:self.locationManager didStartMonitoringForRegion:self.beaconRegion];
Add this method somewhere in the code:

- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {    [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];}

What we are doing is manually calling the didStartMonitoringForRegion method and providing it with the locationManager and defined beaconRegion to work with. It isn’t a great implementation but it works enough for you to do some testing.

The full code can be found below or you can download the project (needs Xcode 5 and devices running iOS 7)from here.

ConfigViewController Header

#import <UIKit/UIKit.h>#import <CoreLocation/CoreLocation.h>#import <CoreBluetooth/CoreBluetooth.h> @interface ConfigViewController : UIViewController <CBPeripheralManagerDelegate> @property (strong, nonatomic) CLBeaconRegion *beaconRegion;@property (weak, nonatomic) IBOutlet UILabel *uuidLabel;@property (weak, nonatomic) IBOutlet UILabel *majorLabel;@property (weak, nonatomic) IBOutlet UILabel *minorLabel;@property (weak, nonatomic) IBOutlet UILabel *identityLabel;@property (strong, nonatomic) NSDictionary *beaconPeripheralData;@property (strong, nonatomic) CBPeripheralManager *peripheralManager; @end
ConfigViewController Implementation

#import "ConfigViewController.h" @interface ConfigViewController () @end @implementation ConfigViewController - (void)viewDidLoad{    [super viewDidLoad];// Do any additional setup after loading the view.    [self initBeacon];    [self setLabels];} - (void)initBeacon {    NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:@"23542266-18D1-4FE4-B4A1-23F8195B9D39"];    self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid                                                                major:1                                                                minor:1                                                           identifier:@"com.devfright.myRegion"];} - (void)setLabels {    self.uuidLabel.text = self.beaconRegion.proximityUUID.UUIDString;    self.majorLabel.text = [NSString stringWithFormat:@"%@", self.beaconRegion.major];    self.minorLabel.text = [NSString stringWithFormat:@"%@", self.beaconRegion.minor];    self.identityLabel.text = self.beaconRegion.identifier;} - (IBAction)transmitBeacon:(UIButton *)sender {    self.beaconPeripheralData = [self.beaconRegion peripheralDataWithMeasuredPower:nil];    self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self                                                                                     queue:nil                                                                                   options:nil];} -(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {    if (peripheral.state == CBPeripheralManagerStatePoweredOn) {        NSLog(@"Powered On");        [self.peripheralManager startAdvertising:self.beaconPeripheralData];    } else if (peripheral.state == CBPeripheralManagerStatePoweredOff) {        NSLog(@"Powered Off");        [self.peripheralManager stopAdvertising];    }} - (void)didReceiveMemoryWarning{    [super didReceiveMemoryWarning];    // Dispose of any resources that can be recreated.} @end
TrackViewController Header

#import <UIKit/UIKit.h>#import <CoreLocation/CoreLocation.h> @interface TrackViewController : UIViewController <CLLocationManagerDelegate> @property (weak, nonatomic) IBOutlet UILabel *beaconFoundLabel;@property (weak, nonatomic) IBOutlet UILabel *proximityUUIDLabel;@property (weak, nonatomic) IBOutlet UILabel *majorLabel;@property (weak, nonatomic) IBOutlet UILabel *minorLabel;@property (weak, nonatomic) IBOutlet UILabel *accuracyLabel;@property (weak, nonatomic) IBOutlet UILabel *distanceLabel;@property (weak, nonatomic) IBOutlet UILabel *rssiLabel; @property (strong, nonatomic) CLBeaconRegion *beaconRegion;@property (strong, nonatomic) CLLocationManager *locationManager; @end
TrackViewController Implementation

#import "TrackViewController.h" @interface TrackViewController () @end @implementation TrackViewController - (void)viewDidLoad{    [super viewDidLoad];// Do any additional setup after loading the view.    self.locationManager = [[CLLocationManager alloc] init];    self.locationManager.delegate = self;    [self initRegion];    [self locationManager:self.locationManager didStartMonitoringForRegion:self.beaconRegion];} - (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {    [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];} - (void)initRegion {    NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:@"23542266-18D1-4FE4-B4A1-23F8195B9D39"];    self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:@"com.devfright.myRegion"];    [self.locationManager startMonitoringForRegion:self.beaconRegion];} - (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {    NSLog(@"Beacon Found");    [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];} -(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {    NSLog(@"Left Region");    [self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];    self.beaconFoundLabel.text = @"No";} -(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {    CLBeacon *beacon = [[CLBeacon alloc] init];    beacon = [beacons lastObject];     self.beaconFoundLabel.text = @"Yes";    self.proximityUUIDLabel.text = beacon.proximityUUID.UUIDString;    self.majorLabel.text = [NSString stringWithFormat:@"%@", beacon.major];    self.minorLabel.text = [NSString stringWithFormat:@"%@", beacon.minor];    self.accuracyLabel.text = [NSString stringWithFormat:@"%f", beacon.accuracy];    if (beacon.proximity == CLProximityUnknown) {        self.distanceLabel.text = @"Unknown Proximity";    } else if (beacon.proximity == CLProximityImmediate) {        self.distanceLabel.text = @"Immediate";    } else if (beacon.proximity == CLProximityNear) {        self.distanceLabel.text = @"Near";    } else if (beacon.proximity == CLProximityFar) {        self.distanceLabel.text = @"Far";    }    self.rssiLabel.text = [NSString stringWithFormat:@"%i", beacon.rssi];} - (void)didReceiveMemoryWarning{    [super didReceiveMemoryWarning];    // Dispose of any resources that can be recreated.} @end


转自:http://www.devfright.com/ibeacons-tutorial-ios-7-clbeaconregion-clbeacon/


                                             
0 0