RACSignal for didRangeBeacons:inRegion: not working - ios

I'm building an app on iOS 10 using the Estimote SDK. I want to use the range mode to detect nearby beacons. The UUID for the region and beacons is setup correctly as verified in a small sample project.
The app I'm building right now is showing some strange behaviour: After starting the app, the beaconManager:didRangeBeacons:inRegion: method is not called even when right next to the beacon.
Disabling / enabling Bluetooth will cause the method to fire immediately. This is also true for pausing the app and resuming it using the debugger.
What is causing this behaviour? I'm requesting the permission at every launch and wait for the callback to start monitoring (as stated in the documentation). I already tried to setup more startRanging / stopRanging calls (desperate!) but no success.
Any ideas?
#implementation Model {}
- (instancetype)init {
self = [super init];
if (self) {
self.beaconManager = [ESTBeaconManager new];
self.beaconManager.delegate = self;
self.beaconRegion = [[CLBeaconRegion alloc]
initWithProximityUUID:[[NSUUID alloc]
initWithUUIDString:proximityUUID]
identifier:#"Playground"];
[self.beaconManager requestWhenInUseAuthorization];
self.beaconSignal = [self rac_signalForSelector:#selector(beaconManager:didRangeBeacons:inRegion:) fromProtocol:#protocol(ESTBeaconManagerDelegate)];
[[self.beaconSignal throttle:1]
subscribeNext:^(id x) {
NSLog(#"Did range fired");
}];
}
return self;
}
- (void)start {
[self.beaconManager startRangingBeaconsInRegion:self.beaconRegion];
}
- (void)beaconManager:(id)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if(status == kCLAuthorizationStatusAuthorizedWhenInUse){
[self start];
}
}

The problem is the throttled subscription to RACSignal for the beaconManager:didRangeBeacons:inRegion:selector. Removing the throttle or the delegate method without a signal works fine.

Related

Crash on CLConnection::sendMessageInternal

I need help trying to figure out this crash.
I have a common crash (#1 crash, ~300 devices affected) that seems to occur when my app is checking CLLocation authorization status:
The 'start' method is as follows:
-(BOOL)start {
if ([self isDenied]) {
return NO;
} else {
_manager = [[CLLocationManager alloc] init];
_manager.delegate = self;
if ([_manager respondsToSelector:#selector(requestAlwaysAuthorization)] && [self isNotDetermined]) {
[_manager requestAlwaysAuthorization];
} else {
[self startUpdatingLocation];
}
return YES;
}
}
The 'isNotDetermined' method, from which the application seems to switch to a different thread and crash, is:
- (BOOL)isNotDetermined {
return CLLocationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined;
}
Maybe also relevant, the isDenied method; according to the documentation, the CLLocationManager should not be created if the authorization status is Denied or Restricted:
- (BOOL)isDenied {
return CLLocationManager.authorizationStatus == kCLAuthorizationStatusDenied;
}
Could it be that the app crashes because I create a CLLocationManager while the authorizationStatus is Restricted? I'd expect the crash to occur in the CLLocationManager init method in that case.
I / we have managed to find the problem. The problem was twofold:
stopUpdatingLocation was not called when the application entered the background.
When the application entered the foreground, the CLLocationManager was re-initialized, without first stopping the old instance. Two bits of code:
// called in both didFinishLaunching and applicationWillEnterForeground
-(void) start {
_manager = [[CLLocationManager alloc] init];
// other setup code
}
// Called in applicationDidEnterBackground
-(void)stop {
// this variable was never false
if (_updateBackgroundLocations) {
[_manager stopUpdatingLocation];
}
}
What I did to fix it:
-(void) start {
if (_manager == nil) {
_manager = [[CLLocationManager alloc] init];
}
// other setup code
}
-(void)stop {
[_manager stopUpdatingLocation];
}
This fixed... several thousand crashes. They probably occurred when the application was already in the background, since we never did get complaints about frequently occurring crashes from our users.

iOS: Why is the outlined location services icon still showing after stopping monitoring for regions?

I am testing on an iPad 3rd gen on iOS 7.1 since I have no other iOS device for the moment.
The first time I run my app, it starts monitoring for several regions. The status bar and the Location Services settings page are showing the outlined location services icon (my app is the only one in the list that has the outlined icon). When I kill my app, the icon is still showing on both places since I do not stop monitoring the regions yet. Until then everything is fine.
My problem is when I run my app for a second time, I stop monitoring for all monitored regions, but the location services outlined icon does not disappear on the status bar and the Location Services settings page...
Here is my code called at the first run :
- (void) getLocationManagerInstance {
if (!self.locationManager) {
self.locationManager = [CLLocationManager new];
}
self.locationManager.delegate = self;
}
- (void) startLocationGathering {
if(self.shouldUpdateGPSLocations) {
[self.locationManager startMonitoringSignificantLocationChanges];
}
}
- (void) startMonitoringBeaconRegions {
if(self.rootRegion) {
[self.locationManager startMonitoringForRegion:self.rootRegion];
}
if (self.beaconRegions && self.beaconRegions.count < 20) {
[self.beaconRegions enumerateObjectsUsingBlock:^(CLBeaconRegion* region, NSUInteger idx, BOOL *stop) {
[self.locationManager startMonitoringForRegion:region];
}];
}
}
- (void) startMonitoringCircularRegions {
if (self.gpsRegions && self.gpsRegions.count) {
[self.gpsRegions enumerateObjectsUsingBlock:^(CLCircularRegion* region, NSUInteger idx, BOOL *stop) {
[self.locationManager startMonitoringForRegion:region];
}];
}
}
And my code called at the second run :
- (void) getLocationManagerInstance {
if (!self.locationManager) {
self.locationManager = [CLLocationManager new];
}
self.locationManager.delegate = self;
}
- (void) locationManagerCleanup {
[AWRUtils dlog:#"locationManagerCleanup"];
NSArray* monitoredRegions = [self.locationManager monitoredRegions].allObjects;
for (CLRegion* r in monitoredRegions) {
[self.locationManager stopMonitoringForRegion:r];
}
NSArray* rangedRegions = [self.locationManager rangedRegions].allObjects;
for (CLBeaconRegion* r in rangedRegions) {
[self.locationManager stopRangingBeaconsInRegion:r];
}
[self.locationManager stopUpdatingLocation];
[self.locationManager stopMonitoringSignificantLocationChanges];
}
If I uninstall my app, the outlined location services icon disappear. But why is the icon not disappearing when I stop monitoring the monitored regions?
EDIT: After more testing, I found that the instance of the CLLocationManager that I have on the second run has no monitored regions ([self.locationManager monitoredRegions] returns nil)...
EDIT 2: I also found that if, on the second run, I start monitoring all the same regions that started monitoring on the first run AND THEN I stop monitoring them, the outlined location services icon disappear. Is this a normal behavior? I read nothing about that in all my internet researches...

CLLocationManager delegate not called

I am using iBeacons, but am running into a small problem.
On first use the user needs to give permission, we then following is called:
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
[_locationManager requestAlwaysAuthorization];
However, I expected the following delegate method to be called:
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
NSLog(#"Auth status changed: %i", status);
if(status > 3){
// Check if we have to start monitoring beacons
NSLog(#"Do we need to initialise after auth given?");
//[self initialiseLocations];
}
}
I am running iOS 8.0.2, so not sure if this is a bug
This answer might help... it solved it for me at least. Also this website was a good walkthrough for this issue as well.
It looks like you are calling the requestAlwaysAuthorization correctly, but do you call startUpdatingLocation anywhere? Also, double check that in your Info.plist you have the appropriate key and string value added (NSLocationAlwaysUsageDescription since you're using requestAlwaysAuthorization).

Using multiples Beacons (poping a view on each different beacon)

I m a beginner in objective C.
My app work correctly with one beacon.
I'm using the "estimote SDK".
I have many problems, i want to use 2 or 3 beacons. I want to push a View for each of the beacons.
I don't understand how i can do it with multiple beacons.
I don't know if i have to use multiple beacon manager. (ESTBeaconManager* beaconManager)
I dont know how to pass differents regions to the didRangeBeacons:(NSArray *)beacons inRegion:(ESTBeaconRegion *)region
Can i use one beacon only for notification and the 2 others to pop 2 differents view when i m close of them. ( one different view for each beacon )
Thanks for your help.
Best Regards.
Code:
#import "ESTViewController.h"
#import "PresentViewController.h"
#import <ESTBeaconManager.h>
#import <AudioToolbox/AudioToolbox.h>
#interface ESTViewController () <ESTBeaconManagerDelegate>
#property (nonatomic, strong) ESTBeaconManager* beaconManager;
#property (nonatomic, strong) ESTBeaconManager* beaconManager2;
#property (nonatomic, strong) ESTBeaconManager* beaconManager3;
#property (nonatomic, strong) ESTBeacon* selectedBeacon;
#end
#implementation ESTViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// should i create one manager instance or more ?
self.beaconManager = [[ESTBeaconManager alloc] init];
self.beaconManager.delegate = self;
self.beaconManager.avoidUnknownStateBeacons = NO;
//self.beaconManager2 = [[ESTBeaconManager alloc] init];
//self.beaconManager2.delegate = self;
//self.beaconManager2.avoidUnknownStateBeacons = NO;
//self.beaconManager3 = [[ESTBeaconManager alloc] init];
//self.beaconManager3.delegate = self;
//self.beaconManager3.avoidUnknownStateBeacons = NO;
// My Differents regions
region = [[ESTBeaconRegion alloc] initWithProximityUUID:ESTIMOTE_PROXIMITY_UUID
major:12800 minor:228 identifier:#"Icy Marshmellow"];
region2 = [[ESTBeaconRegion alloc] initWithProximityUUID:ESTIMOTE_PROXIMITY_UUID
major:12800 minor:128 identifier:#"Mint Cocktail"];
region3 = [[ESTBeaconRegion alloc] initWithProximityUUID:ESTIMOTE_PROXIMITY_UUID
major:12800 minor:328 identifier:#"Blueberry Pie"];
// Should i do it for each region with one ESTBeaconManager or 3 ?
[self.beaconManager requestStateForRegion:region];
[self.beaconManager requestStateForRegion:region2];
[self.beaconManager requestStateForRegion:region3];
}
// NOTIFICATION METHOD :
-(void)beaconManager:(ESTBeaconManager *)manager
didEnterRegion:(ESTBeaconRegion *)region
{
// iPhone/iPad entered beacon zone
// present local notification
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = #"Hello blabla blabla";
notification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
-(void)beaconManager:(ESTBeaconManager *)manager
didExitRegion:(ESTBeaconRegion *)region
{
// iPhone/iPad left beacon zone
// present local notification
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = #"bye bye";
notification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.beaconManager startRangingBeaconsInRegion:region];
[self.beaconManager startMonitoringForRegion:region];
//[self.beaconManager2 startRangingBeaconsInRegion:region2];
//[self.beaconManager2 startMonitoringForRegion:region2];
//[self.beaconManager3 startRangingBeaconsInRegion:region3];
//[self.beaconManager3 startMonitoringForRegion:region3];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.beaconManager stopRangingBeaconsInRegion:region];
[self.beaconManager stopMonitoringForRegion:region];
//[self.beaconManager2 stopRangingBeaconsInRegion:region2];
//[self.beaconManager2 stopMonitoringForRegion:region2];
//[self.beaconManager3 stopRangingBeaconsInRegion:region3];
//[self.beaconManager3 stopMonitoringForRegion:region3];
}
// My problem is here , i dont know how i can pass differents regions here
-(void)beaconManager:(ESTBeaconManager *)manager
didRangeBeacons:(NSArray *)beacons
inRegion:(ESTBeaconRegion *)region
{
if([beacons count] > 0)
{
if(!self.selectedBeacon)
{
// initialy pick closest beacon
self.selectedBeacon = [beacons objectAtIndex:0];
}
else
{
for (ESTBeacon* cBeacon in beacons)
{
// update beacon it same as selected initially
if([self.selectedBeacon.major unsignedShortValue] == [cBeacon.major unsignedShortValue] &&
[self.selectedBeacon.minor unsignedShortValue] == [cBeacon.minor unsignedShortValue])
{
self.selectedBeacon = cBeacon;
}
}
}
switch (self.selectedBeacon.proximity)
{
case CLProximityUnknown:
{
self.rangeStatusImageView.image = [UIImage imageNamed:#"logo_signal.jpg"];
self.descriptionStateLabel.text = #"Signal lost";
break;
}
case CLProximityImmediate:
{
[self performSegueWithIdentifier: #"presentSegue" sender: self];
break;
}
case CLProximityNear:
{
self.rangeStatusImageView.image = [UIImage imageNamed:#"logo_near_bleu.jpg"];
self.descriptionStateLabel.text = #"Come closer";
break;
}
case CLProximityFar:
{
self.rangeStatusImageView.image = [UIImage imageNamed:#"logo_far_clair.jpg"];
self.descriptionStateLabel.text = #"Welcome";
break;
}
default:
break;
}
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
EDIT
Ok i worked on my code and now i do it with one region. my array of beacons have 3 beacons.
region = [[ESTBeaconRegion alloc] initWithProximityUUID:ESTIMOTE_PROXIMITY_UUID identifier:#"multibeacons"];
i dont use major or minor at the init.
in ViewDidAppears i do :
[self.beaconManager startRangingBeaconsInRegion:region];
The delegate like this :
-(void)beaconManager:(ESTBeaconManager *)manager
didRangeBeacons:(NSArray *)beacons
inRegion:(ESTBeaconRegion *)region
{
// I used a sort , sorting by distance
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"distance" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
// if breakpoint here 3 beacons in array
self.beaconsArray = [beacons sortedArrayUsingDescriptors:sortDescriptors];
if([self.beaconsArray count] > 0)
{
if(!self.selectedBeacon)
{
// initialy pick closest beacon
self.selectedBeacon = [beacons objectAtIndex:0];
currentBeaconMinor = self.selectedBeacon.minor;
}
else
{
for (ESTBeacon* cBeacon in self.beaconsArray)
{
// update beacon it same as selected initially
if([self.selectedBeacon.major unsignedShortValue] == [cBeacon.major unsignedShortValue] &&
[self.selectedBeacon.minor unsignedShortValue] == [cBeacon.minor unsignedShortValue])
{
self.selectedBeacon = cBeacon;
currentBeaconMinor = self.selectedBeacon.minor;
}
}
}
I sort by distance and i have a currentBeaconMinor Value. My array of beacon have 3 beacons inside if i put a breakpoint i can see 3.
In the Switch proximity , i have do like this :
switch (self.selectedBeacon.proximity)
{
case CLProximityImmediate:
{
if ([currentBeaconMinor floatValue] == 128)
{
NSLog(#"128 128 128");
//[self performSegueWithIdentifier: #"presentSegue1" sender: self];
}
else if ([currentBeaconMinor floatValue] == 228)
{
NSLog(#"228 228 228");
//[self performSegueWithIdentifier: #"presentSegue2" sender: self];
}
else if ([currentBeaconMinor floatValue] == 328)
{
NSLog(#"328 328 328");
//[self performSegueWithIdentifier: #"presentSegue3" sender: self];
}
break;
}
But that still dont work :((( I m getting mad. My app pick initialy the closest beacon. After that app always keep the same beacon and never change. I move the beacon near the device but nslog always send me the same minor number. Please can you give me some help ? I m sure i m doing something wrong.
Each beacon has 3 pieces of information - a UUID, a major number and a minor number. When you create a beacon region you must specify the UUID as a minimum. You can optionally specify a major and minor value. There is a limit to the number of beacon regions that iOS will scan for in the background (I believe it is 20), so generally it is best to be as broad as possible in your region registration and then determine when notified if you are interested in the beacon that is visible.
All Estimote beacons have the same UUID, so if you register a region with just the UUID your app will be notified whenever you are in range of any Estimote beacon. I can see that your three beacons are all using major 12800, so you could simply create a region that specified the UUID and the major and then you would be notified whenever a beacon with those values was visible - or you can do as you have done and register specific regions for each of your three beacons.
You only need a single EstBeaconManager instance to manage all regions.
Whenever you enter or exit a region your didEnterRegion and didExitRegion methods will be called. Also your didRangeBeacons method will be called if you are currently ranging beacons.
In these delegate methods you need to examine the major & minor (well, really just the minor, because in your case the major is always the same) values to determine which action you want to take. You can also examine the region identifier string to determine which beacon is visible.
Note that more than one beacon may be in range, so you may need to examine the proximities to determine which action you want to take.
Finally, although you define the three regions, you are not ranging/monitoring them all, so change your viewWillAppear to
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.beaconManager startRangingBeaconsInRegion:region];
[self.beaconManager startMonitoringForRegion:region];
[self.beaconManager startRangingBeaconsInRegion:region2];
[self.beaconManager startMonitoringForRegion:region2];
[self.beaconManager startRangingBeaconsInRegion:region3];
[self.beaconManager startMonitoringForRegion:region3];
}
I haven't used the Estimote SDK before. I just treat my Estimote beacons as vanilla iBeacons and use Apple's Location Manager framework.
It looks to me like you are very confused.
I looked at the Estimote SDK briefly, and it looked quite similar to Apple's location manager.
In the location manager, you create a beacon region, then you ask the location manager to start ranging for that beacon region (which tells you about estimated distance readings or you use startMonitoringForRegion to ask to be notified about enter/exit region events.
You can do both at the same time. Glancing at the Estimate APIs, it looks like they use the same approach.
You need to create a beacon region, then call startRangingBeaconsInRegion and/or startMonitoringForRegion to ask for range and/or enter/exit region notifications.
You then wait for your delegate method(s) to be called when a beacon enters/exits range, (startMonitoringForRegion) or changes distance (startRangingBeaconsInRegion)
Looking at the docs, it seems that calling requestStateForRegion will cause the Estimote beacon manager to call your locationManager:didDetermineState:forRegion: delegate method once and only once.
In your code, you haven't asked to monitor for regions or to range beacons, so all 3 regions will return a "not in range" state.
Don't call those requestStateForRegion. Call startMonitoringForRegion and/or startRangingBeaconsInRegion, and wait for the system to notify you when the beacon status changes.

Assertion Failure with Estimote

I'm having some problems when I try and run my "xcode" project and I get this "run time" error.
Assertion failure in -[CLLocationManager startRangingBeaconsInRegion:], /SourceCache/CoreLocationFramework/CoreLocation-1613.35/Framework/CoreLocation/CLLocationManager.m:991
2014-05-11 15:47:15.483 Ziew[1516:60b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: region != nil'
I'm trying use the "Estimote SDK" to build a similar application using the proximity application code example. The original example works great and I didn't change anything when I added their code. Here some of my methods:
- (id)initWithBeacon:(ESTBeacon *)beacon
{
self = [super init];
if (self)
{
self.beacon = beacon;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[UITabBar appearance] setSelectedImageTintColor:[UIColor colorWithRed:(141/255.0) green:(198/255.0) blue:(63/255.0) alpha:1]];
// Do any additional setup after loading the view.
//Beacon Manager setup
self.beaconManager = [[ESTBeaconManager alloc] init];
self.beaconManager.delegate = self;
self.beaconRegion = [[ESTBeaconRegion alloc] initWithProximityUUID:self.beacon.proximityUUID
major:[self.beacon.major unsignedIntValue]
minor:[self.beacon.minor unsignedIntValue]
identifier:#"RegionIdentifier"];
[self.beaconManager startRangingBeaconsInRegion:self.beaconRegion];
}
#pragma mark - ESTBeaconManager delegate
- (void)beaconManager:(ESTBeaconManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(ESTBeaconRegion *)region
{
if (beacons.count > 0)
{
ESTBeacon *firstBeacon = [beacons firstObject];
[self textForProximity:firstBeacon.proximity];
}
NSLog(#"No beacons within region");
}
#pragma mark -
- (void)textForProximity:(CLProximity)proximity
{
switch (proximity) {
case CLProximityFar:
NSLog(#"Far");
break;
case CLProximityNear:
NSLog(#"Near");
break;
case CLProximityImmediate:
NSLog(#"Immediate");
break;
default:
NSLog(#"Unknown");
break;
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[self.beaconManager stopRangingBeaconsInRegion:self.beaconRegion];
[super viewDidDisappear:animated];
}
What am I missing?
SOLUTION
Thank you to everyone who responded. Using the Estimote SDK you can assign a constant UUID with the beacon corresponding major and minor id to get the startRangingBeaconsInRegion method called.
self.beaconRegion = [[ESTBeaconRegion alloc] initWithProximityUUID:ESTIMOTE_PROXIMITY_UUID
major:your_major_id
minor:your_minor_id
identifier:#"RegionIdentifier"];
From what I can tell your beacon region is nil by the time you tell it to start ranging beacons. Since you assign to your property an initialized instance of a region, maybe your property declaration is listed as weak instead of strong?
You have probably copied initWithBeacon constructor from their demos and this constructor is not being called when view controller is being instantiated (like from xib or storyboard).
Because self.beacon is nil, constructed ESTRegion object is allocated with nil params which causes crash.
You need to:
range for beacons around you first
or
hardcode beacon identifiers in your app (or fetch it from somewhere)

Resources