Skip to content

Instantly share code, notes, and snippets.

@izotx
Last active September 21, 2018 07:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save izotx/89a14eb610a0b1bd902b to your computer and use it in GitHub Desktop.
Save izotx/89a14eb610a0b1bd902b to your computer and use it in GitHub Desktop.
Objective-C iBeacon Manager
//
// JMCBeaconManager.h
// iBeaconTest
//
/**
* Class that can be used to monitor nearby beacons. To use it you should follow the steps:
1. Check if beacons are supported calling isSupported method
2. Register regions to monitor
3. Specify beaconFound block to monitor beacons.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@import CoreLocation;
@interface JMCBeaconManager : NSObject
@property BOOL logging;
//property that verifies that ibeacons is supported
@property BOOL beaconsSupported;
/**Description: start monitoring services for all regions*/
-(void)startMonitoring;
/**Description: stop monitoring services for all regions*/
-(void)stopMonitoring;
/**
* Description: Registers beacon's region using major and minor identifier
*
* @param UUID beacon identifier
* @param identifier beacon identifier used internally
* @param major major identifier
* @param minor minor identifier
*/
-(void)registerRegionWithProximityId:(NSString*)UUID andIdentifier:(NSString *)identifier major:(int)major andMinor:(int)minor;
/**
* Checks if beacon is supported
*
* @param message string that will contain message about problems with supporting the beacons
*
* @return boolean that indicates if device supports beacons or not
*/
-(BOOL)isSupported;
/**
* Appends message to the log file
*
* @param message message to save
*/
-(void)saveLog:(NSString *)message;
/**
* Getting the log
*
* @return log as string
*/
-(NSString *)getLog;
/**
* View used for loggin the events
*/
@property(nonatomic,strong)UITextView * logView;
/**
* Block used for monitoring and processing the beacons
*/
@property(nonatomic, copy) void (^beaconFound)(NSString * uuid, int major, int minor, CLProximity proximity, CLLocationAccuracy accuracy, NSInteger rssi);
@property(nonatomic, copy) void (^nearbyBeacons)(NSArray * beacons);
/**
* Block used for monitoring region events
*/
@property(nonatomic, copy) void (^regionEvent)(NSString * uuid, int major, int minor, CLRegionState state);
@end
//
// JMCBeaconManager.m
// iBeaconTest
//
// Created by sadmin on 2/21/14.
// Copyright (c) 2014 JanuszChudzynski. All rights reserved.
//
#import "JMCBeaconManager.h"
@import AudioToolbox;
@import CoreBluetooth;
@interface JMCBeaconManager()<CLLocationManagerDelegate, CBCentralManagerDelegate>
{
CLProximity proximity;
int counter;
}
@property(nonatomic,strong)CLLocationManager * locationManager;
@property(nonatomic,strong) CLBeacon * currentBeacon;
@property(nonatomic,strong) NSMutableArray * regions;
@property(nonatomic,strong) NSMutableDictionary * stateDictionary;
@property(nonatomic,strong) CBCentralManager * bluetoothManager;
@property BOOL bluetoothEnabled;
@end
@implementation JMCBeaconManager
/**Sometimes ios doesn't call did enter region in this case we will call it manually*/
-(void)updateState:(CLBeaconRegion *)region state:(CLRegionState)state{
if([[_stateDictionary objectForKey:region.identifier]integerValue]!=state ){
[_stateDictionary setObject:@(state) forKey:region.identifier];
}
}
/***/
-(void)checkStateForRegion:(CLBeaconRegion *)region{
if([[_stateDictionary objectForKey:region.identifier]integerValue]!=CLRegionStateInside){
[self.locationManager requestStateForRegion:region];
}
}
- (void)startBluetoothStatusMonitoring {
self.bluetoothManager = [[CBCentralManager alloc]
initWithDelegate:self
queue:dispatch_get_main_queue()
options:@{CBCentralManagerOptionShowPowerAlertKey: @(NO)}];
}
#pragma mark CLLocationManager Delegate
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways) {
// user allowed
if( [self isSupported]){
[self startMonitoring];
}
}
}
#pragma mark - CBCentralManagerDelegate
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
if ([central state] == CBCentralManagerStatePoweredOn) {
self.bluetoothEnabled = YES;
if([self isSupported]){
[self startMonitoring];
}
}
else {
self.beaconsSupported = false;
self.bluetoothEnabled = NO;
[self logMessage:@"You must enable bluetooth!"];
}
}
-(void)logMessage:(NSString *)message{
message =[NSString stringWithFormat:@"%@\r\n %@ \r\n %@", [NSDate new],message,self.logView.text];
self.logView.text = message;
NSLog(@"\n %@ \n ",message);
// [self saveLog:message];
}
-(id)init{
self = [super init];
if(self){
_locationManager = [[CLLocationManager alloc]init];
_regions = [NSMutableArray new];
_locationManager.delegate = self;
counter =0;
_stateDictionary = [NSMutableDictionary new];
//Request Permission
[self startBluetoothStatusMonitoring];
[self.locationManager requestAlwaysAuthorization];
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationBackgroundRefreshStatusDidChangeNotification
object: [UIApplication sharedApplication]
queue:nil
usingBlock:^(NSNotification* notification){
NSLog(@"Just changed background refresh status because of this notification:%@",notification);
if( [self isSupported]){
[self startMonitoring];
}
}];
}
return self;
}
/**
Checks if iBeacon monitoring is supported
*/
-(BOOL)isSupported{
NSMutableString*message = [[NSMutableString alloc]initWithCapacity:0];
BOOL enabled = NO;
NSMutableString * msg = [NSMutableString new];
[msg appendFormat:@""];
if([CLLocationManager isMonitoringAvailableForClass:[CLRegion class]]){
enabled = YES;
}
else{
enabled = NO;
[message appendFormat:@"%@ /n %@ ",message, @"Region Monitoring is not available on this device"];
}
if([CLLocationManager authorizationStatus ]== kCLAuthorizationStatusAuthorizedAlways ){
enabled = YES && enabled;
}
else{
enabled = NO;//&&enabled;
[message appendFormat:@"%@ /n %@ ",message, @"Applications must be explicitly authorized to use location services by the user and location services must themselves currently be enabled for the system."];
}
/*Background Refreshing*/
if ([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusAvailable) {
enabled = YES && enabled;
}else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusDenied)
{
[message appendFormat:@"%@ /n %@ ",message, @"The user explicitly disabled background behavior for this app or for the whole system."];
enabled = NO;// && enabled;
}
else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusRestricted)
{
[message appendFormat:@"%@ /n %@ ",message, @"unavailable on this system due to device configuration; the user cannot enable the feature."];
enabled = NO;//&&enabled;
}
if(enabled) {
message = [@"iBeacon monitoring is supported" mutableCopy];
}
if(self.logging){
[self logMessage:message];
}
self.beaconsSupported = enabled;
return enabled;
}
-(BOOL)isEnabled{
return [CLLocationManager isMonitoringAvailableForClass:[CLRegion class]] &&[CLLocationManager authorizationStatus ]== kCLAuthorizationStatusAuthorizedAlways;
}
-(BOOL)canDeviceSupportAppBackgroundRefresh
{
// Override point for customization after application launch.
if ([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusAvailable) {
NSString * message = @"Background updates are available for the app.";
if(self.logging){
[self logMessage:message];
}
return YES;
}else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusDenied)
{
NSString * message = @"The user explicitly disabled background behavior for this app or for the whole system.";
if(self.logging){
[self logMessage:message];
}
return NO;
}
else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusRestricted)
{
NSString * message = @"Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user.";
if(self.logging){
[self logMessage:message];
}
return NO;
}
return NO;
}
/**Start monitoring regions */
-(void)startMonitoring{
for (CLBeaconRegion * beaconRegion in self.regions) {
[self.locationManager startMonitoringForRegion:beaconRegion];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
[self.locationManager startUpdatingLocation];
[self.locationManager performSelector:@selector(requestStateForRegion:) withObject:beaconRegion afterDelay:1];
}
}
//Stops Monitoring Services
-(void)stopMonitoring;{
for (CLBeaconRegion * beaconRegion in self.regions) {
[self.locationManager stopMonitoringForRegion:beaconRegion];
[self.locationManager stopRangingBeaconsInRegion:beaconRegion];
[self.locationManager stopUpdatingLocation];
}
}
/**
Register beacons only using identifier and proximity uiid
*/
-(void)registerBeaconWithProximityId:(NSString*)pid andIdentifier:(NSString *)identifier{
NSUUID *proximityUUID = [[NSUUID alloc]
initWithUUIDString:pid];
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc]initWithProximityUUID:proximityUUID identifier:identifier];
beaconRegion.notifyOnEntry=YES;
beaconRegion.notifyOnExit=YES;
beaconRegion.notifyEntryStateOnDisplay=YES;
[self.regions addObject:beaconRegion];
}
/**
Estimote beacons use a fixed Proximity UUID of B9407F30-F5F8-466E-AFF9-25556B57FE6D.
Each beacon has a unique ID formatted as follows: proximityUUID.major.minor. We reserved the proximityUUID for all our beacons. The major and minor values are randomized by default but can be customized.
*/
-(void)registerRegionWithProximityId:(NSString*)pid andIdentifier:(NSString *)identifier major:(int)major andMinor:(int)minor{
NSUUID *proximityUUID = [[NSUUID alloc]
initWithUUIDString:pid];
if(major==-1 && minor==-1){
[self registerBeaconWithProximityId:pid andIdentifier:identifier];
return;
}
CLBeaconRegion *beaconRegion;
beaconRegion= [[CLBeaconRegion alloc]initWithProximityUUID:proximityUUID major:major minor:minor identifier:identifier];
beaconRegion.notifyOnEntry=YES;
beaconRegion.notifyOnExit=YES;
beaconRegion.notifyEntryStateOnDisplay=YES;
[self.regions addObject:beaconRegion];
}
/**Tells the delegate that the user enter specified region.*/
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
if(self.logging){
NSString * log = [NSString stringWithFormat:@"%s",__PRETTY_FUNCTION__];
[self logMessage:log];
}
if([region isKindOfClass:[CLBeaconRegion class]]){
[self.locationManager startRangingBeaconsInRegion:(CLBeaconRegion *) region];
}
}
/** Tells the delegate that the user left the specified region.*/
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
if(self.logging){
NSString * log = [NSString stringWithFormat:@"%s",__PRETTY_FUNCTION__];
[self logMessage:log];
}
proximity = -1;
if([region isKindOfClass:[CLBeaconRegion class]]){
[self.locationManager stopRangingBeaconsInRegion:(CLBeaconRegion *)region];
}
}
/** Tells the delegate about the state of the specified region. (required) */
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
NSString * log = [NSString stringWithFormat:@"%s",__PRETTY_FUNCTION__];
if([region isKindOfClass:[CLBeaconRegion class]]){ //check if the region is beacon region
[self updateState:(CLBeaconRegion *)region state:state];
if(self.logging){
[self logMessage:log];
[self logMessage:[NSString stringWithFormat:@"State for region: %@ is: %d %@ %@",region, (int)state, [(CLBeaconRegion *) region major], [(CLBeaconRegion *) region minor]]];
}
if(self.regionEvent){
self.regionEvent( [[(CLBeaconRegion *) region proximityUUID]UUIDString], [[(CLBeaconRegion *) region major]intValue],[[(CLBeaconRegion *) region minor]intValue],(NSUInteger)state );
}
if(state == CLRegionStateInside){
//start ranging beacons
[self.locationManager startRangingBeaconsInRegion:(CLBeaconRegion *) region];
[self locationManager:self.locationManager didEnterRegion:region];
}
if(state == CLRegionStateOutside){
//start ranging beacons
[self.locationManager stopRangingBeaconsInRegion:(CLBeaconRegion *) region];
[self locationManager:self.locationManager didExitRegion:region];
}
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
// Delegate of the location manager, when you have an error
NSLog(@"didFailWithError: %@", error);
// UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"application_name", nil) message:NSLocalizedString(@"location_error", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", nil) otherButtonTitles:nil];
// [errorAlert show];
[self logMessage:error.debugDescription];
}
/**Tells the delegate that one or more beacons are in range. */
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region{
if(self.nearbyBeacons){
self.nearbyBeacons(beacons);
}
for(CLBeacon *beacon in beacons)
{
[self checkStateForRegion:region];
proximity = beacon.proximity;
// [self logMessage:[NSString stringWithFormat:@"Beacon range: %@",beacon]];
if(self.beaconFound){
self.beaconFound(beacon.proximityUUID.UUIDString, beacon.major.intValue, beacon.minor.intValue, beacon.proximity, beacon.accuracy,beacon.rssi);
if(self.logging){
NSString *message = [NSString stringWithFormat:@"Proximity: %ld", beacon.proximity];
[self logMessage:message];
//[self log:message];
}
}
}
if(counter>30==1){
//[self logMessage:[NSString stringWithFormat:@"%s",__PRETTY_FUNCTION__]];
counter =0;
}
counter++;
}
/** this method can be used to display a content related to the closest beacon */
-(void)displayContentFor:(CLBeacon * )beacon andRegion:(CLRegion *)region{
if(!_currentBeacon){
_currentBeacon = beacon;
}
else{
if([_currentBeacon.proximityUUID isEqual:_currentBeacon.proximityUUID]&&_currentBeacon.major ==_currentBeacon.major&&_currentBeacon.minor == _currentBeacon.minor)
{
if(_currentBeacon.proximity == beacon.proximity){
//don't change it
//get content for beacon
} //same beacon but different proximity
else{
}
}
}
}
/** Tells the delegate that an error occurred while gathering ranging information for a set of beacons. */
- (void)locationManager:(CLLocationManager *)manager rangingBeaconsDidFailForRegion:(CLBeaconRegion *)region withError:(NSError *)error{
NSString * log = [NSString stringWithFormat:@"Failed: %@ %s", error, __PRETTY_FUNCTION__];
if(self.logging){
[self logMessage:log];
}
}
/** Tells the delegate that a new region is being monitored.*/
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region{
[self.locationManager requestStateForRegion:region];
NSString * log = [NSString stringWithFormat:@"%s",__PRETTY_FUNCTION__];
if(self.logging){
[self logMessage:log];
}
//NSLog(@"%@",log);
// NSLog(@"%@",region);
}
/** Tells the delegate that the delivery of location updates has resumed.*/
- (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager{
NSString * log = [NSString stringWithFormat:@"%s",__PRETTY_FUNCTION__];
if(self.logging){
[self logMessage:log];
NSLog(@"%@ %s",log, __PRETTY_FUNCTION__);
}
}
/**
* Get content of a log file as a string
*
* @return string with log information
*/
-(NSString *)getLog{
NSString * log = @"";
NSString * docs = [self applicationDocumentsDirectory];
NSString * filePath = [docs stringByAppendingPathComponent:@"log.txt"];
if([[NSFileManager defaultManager]fileExistsAtPath:filePath]){
NSError *e;
NSString * existingFile = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&e];
if(e){
NSLog(@"%s %@",__PRETTY_FUNCTION__, e.debugDescription);
}
else{
log = existingFile;
}
}
return log;
}
/**
* Save's a log to txt log file
*
* @param string Message to save
*/
-(void)saveLog:(NSString *)string{
NSString * stringToSave= @"";
NSString * docs = [self applicationDocumentsDirectory];
NSString * filePath = [docs stringByAppendingPathComponent:@"log.txt"];
if([[NSFileManager defaultManager]fileExistsAtPath:filePath]){
NSError *e;
// NSData * d= [NSData dataWithContentsOfFile:filePath];
NSString * existingFile = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&e];
if(e){
NSLog(@"%s %@",__PRETTY_FUNCTION__, e.debugDescription);
}
else{
stringToSave = [NSString stringWithFormat:@"/r/n %@ %@", existingFile, string];
}
}
else{
stringToSave = string;
}
NSData *d = [stringToSave dataUsingEncoding:NSUTF8StringEncoding];
[d writeToFile:filePath atomically:YES];
}
/**
* Immortal documents path
*
* @return document's path
*/
- (NSString *) applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment