I would like to use the iBeacon Feature in an iPad App to act as a beacon transmitter and an iphone App to receive the beacons. I was able to build both apps accordingly but now I ran into some strange problems:
iBeacon Transmitter the iPad app acts as the transmitter of the beacon signal. I implemented an action sheet for selecting a beacon ID I would like to transmit. This is the code for that:
#import "BeaconAdvertisingService.h"
@import CoreBluetooth;
NSString *const kBeaconIdentifier = @"identifier";
@interface BeaconAdvertisingService () <CBPeripheralManagerDelegate>
@property (nonatomic, readwrite, getter = isAdvertising) BOOL advertising;
@end
@implementation BeaconAdvertisingService {
CBPeripheralManager *_peripheralManager;
}
+ (BeaconAdvertisingService *)sharedInstance {
static BeaconAdvertisingService *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
return self;
}
- (BOOL)bluetoothStateValid:(NSError **)error {
BOOL bluetoothStateValid = YES;
switch (_peripheralManager.state) {
case CBPeripheralManagerStatePoweredOff:
if (error != NULL) {
*error = [NSError errorWithDomain:@"identifier.bluetoothState"
code:CBPeripheralManagerStatePoweredOff
userInfo:@{@"message": @"You must turn Bluetooth on in order to use the beacon feature"}];
}
bluetoothStateValid = NO;
break;
case CBPeripheralManagerStateResetting:
if (error != NULL) {
*error = [NSError errorWithDomain:@"identifier.bluetoothState"
code:CBPeripheralManagerStateResetting
userInfo:@{@"message" : @"Bluetooth is not available at this time, please try again in a moment."}];
}
bluetoothStateValid = NO;
break;
case CBPeripheralManagerStateUnauthorized:
if (error != NULL) {
*error = [NSError errorWithDomain:@"identifier.bluetoothState"
code:CBPeripheralManagerStateUnauthorized
userInfo:@{@"message": @"This application is not authorized to use Bluetooth, verify your settings or check with your device's administration"}];
}
bluetoothStateValid = NO;
break;
case CBPeripheralManagerStateUnknown:
if (error != NULL) {
*error = [NSError errorWithDomain:@"identifier.bluetoothState"
code:CBPeripheralManagerStateUnknown
userInfo:@{@"message": @"Bluetooth is not available at this time, please try again in a moment"}];
}
bluetoothStateValid = NO;
break;
case CBPeripheralManagerStateUnsupported:
if (error != NULL) {
*error = [NSError errorWithDomain:@"identifier.blueetoothState"
code:CBPeripheralManagerStateUnsupported
userInfo:@{@"message": @"Your device does not support bluetooth. You will not be able to use the beacon feature"}];
}
bluetoothStateValid = NO;
break;
case CBPeripheralManagerStatePoweredOn:
bluetoothStateValid = YES;
break;
}
return bluetoothStateValid;
}
- (void)startAdvertisingUUID:(NSUUID *)uuid major:(CLBeaconMajorValue)major minor:(CLBeaconMinorValue)minor {
NSError *bluetoothStateError = nil;
if (![self bluetoothStateValid:&bluetoothStateError]) {
NSString *title = @"Bluetooth Issue";
NSString *message = bluetoothStateError.userInfo[@"message"];
[[[UIAlertView alloc] initWithTitle:title
message:message
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
return;
}
CLBeaconRegion *region;
if (uuid && major && minor) {
region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid major:major minor:minor identifier:kBeaconIdentifier];
} else if (uuid && major) {
region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid major:major identifier:kBeaconIdentifier];
} else if (uuid) {
region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:kBeaconIdentifier];
} else {
[NSException raise:@"You must at least provide a UUID to start advertising" format:nil];
}
NSDictionary *peripheralData = [region peripheralDataWithMeasuredPower:nil];
[_peripheralManager startAdvertising:peripheralData];
}
- (void)stopAdvertising {
[_peripheralManager stopAdvertising];
self.advertising = NO;
}
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
NSError *bluetoothStateError = nil;
if (![self bluetoothStateValid: &bluetoothStateError]) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *bluetoothIssueAlert = [[UIAlertView alloc] initWithTitle:@"Bluetooth Problem"
message:bluetoothStateError.userInfo[@"message"]
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[bluetoothIssueAlert show];
});
}
}
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
[[[UIAlertView alloc] initWithTitle:@"Cannot Advertise Beacon"
message:@"There was an issue starting the advertisement of the beacon"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
} else {
NSLog(@"Advertising");
self.advertising = YES;
}
});
}
As far as I can see, the transmitting works perfectly fine...
The iphone App I would like to respond to the received signal ID should throw an local notification as soon as it received the ID. This works perfectly fine on the first run. I can select every of the 3 beacons in the ipad action sheet to throw this notification on the iphone. But when I reselect the first beacon for example nothing happens any more. For the purpose of the app it would be crucial the app responds every time it receives the beacon. I setup the iphone code as follows:
#import "BeaconMonitoringService.h"
#import "LocationManagerService.h"
@implementation BeaconMonitoringService {
CLLocationManager *_locationManager;
}
+ (BeaconMonitoringService *)sharedInstance {
static dispatch_once_t onceToken;
static BeaconMonitoringService *_sharedInstance;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
_locationManager = [[LocationManagerService sharedInstance] getLocationManager];
return self;
}
- (void)startMonitoringBeaconWithUUID:(NSUUID *)uuid major:(CLBeaconMajorValue)major minor:(CLBeaconMinorValue)minor identifier:(NSString *)identifier onEntry:(BOOL)entry onExit:(BOOL)exit {
CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid major:major minor:minor identifier:identifier];
region.notifyOnEntry = entry;
region.notifyOnExit = exit;
region.notifyEntryStateOnDisplay = YES;
[_locationManager startMonitoringForRegion:region];
}
- (void)stopMonitoringAllRegions {
for (CLRegion *region in _locationManager.monitoredRegions) {
[_locationManager stopMonitoringForRegion:region];
}
}
@end
The location manager throws its delegate calls accordingly and is implemented by me in a locationmanagerservice.
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
if ([region isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region;
Beacon *beacon = [[BeaconDetailService sharedService] beaconWithUUID:beaconRegion.proximityUUID];
if (beacon) {
NSDictionary *userInfo = @{@"beacon": beacon, @"state": @(state)};
[[NSNotificationCenter defaultCenter] postNotificationName:@"DidDetermineRegionState" object:self userInfo:userInfo];
}
NSLog(@"Call DidDetermine");
}
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
dispatch_async(dispatch_get_main_queue(), ^{
if ([region isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region;
Beacon *beacon = [[BeaconDetailService sharedService] beaconWithUUID:beaconRegion.proximityUUID];
if (beacon) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.userInfo = @{@"uuid": beacon.uuid.UUIDString};
notification.alertBody = [NSString stringWithFormat:@"Test Beacon %@", beacon.name];
notification.soundName = @"Default";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
[[NSNotificationCenter defaultCenter] postNotificationName:@"DidEnterRegion" object:self userInfo:@{@"beacon": beacon}];
NSLog(@"Call DidEnter");
}
}
});
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
dispatch_async(dispatch_get_main_queue(), ^{
if ([region isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region;
Beacon *beacon = [[BeaconDetailService sharedService] beaconWithUUID:beaconRegion.proximityUUID];
if (beacon) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = [NSString stringWithFormat:@"Test %@", beacon.name];
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
[[NSNotificationCenter defaultCenter] postNotificationName:@"DidExitRegion" object:self userInfo:@{@"beacon": beacon}];
NSLog(@"Call DidExit");
}
}
});
}
When I am logging the call of the delegate methods I receive the following scheme:
1) DidDetermineState is being called 2) DidEnterRegion is being called 3) but no DidExitRegion is called after that.
also I repeatedly receive this error: "PBRequester failed with Error Error Domain=NSURLErrorDomain Code=-1003 "A server with the specified hostname could not be found." UserInfo=0x166875e0 {NSErrorFailingURLStringKey=https://gsp10-ssl.apple.com/use, NSErrorFailingURLKey=https://gsp10-ssl.apple.com/use, NSLocalizedDescription=A server with the specified hostname could not be found., NSUnderlyingError=0x1656a9b0 "A server with the specified hostname could not be found."}"
this seems very strange.. is there a way I can accomplish to receive the local note every time I select a beacon in the action sheet on my ipad?
Interesting enough, when I leave the Beacon I selected turned on, my iphone keeps throwing up the local notes from time to time without me changing anything in between. And suddenly the DidExitRegion is being called and DidEnterRegion after that again...
thank you!!