frostney / react-native-ibeacon

:satellite: iBeacon support for React Native
MIT License
375 stars 54 forks source link

iOS - Background mode doesn´t work #25

Open marseca opened 8 years ago

marseca commented 8 years ago

Version

0.6.0

Hi!

I am trying that my App run this console.log() when he finds the beacon. It works until I change to Background Mode. Then, the logs stops to show the beacons.

I set the background modes like the tutorial says: Location updates, Use Bluetooth LE accesories and I added Remote notifications ( I want the App send you a Local Notification when it finds a beacon)

My code:

class BeaconManager {
    static f_ids = []; // Beacon identifiers that we have found
    static region = {
        identifier: 'eBeacon',
        uuid: '6269736F-6E74-6500-0000-000000000000'
    };

    static Start() {
        // Request for authorization
        Beacons.requestAlwaysAuthorization();

        Beacons.startMonitoringForRegion(this.region);
        Beacons.startRangingBeaconsInRegion(this.region);

        Beacons.startUpdatingLocation();

        // Listen for beacon changes
        DeviceEventEmitter.addListener(
          'beaconsDidRange',
          (data) => {
           console.log(data); // It works until change to Background mode
          }
        );
    }
}

Steps to reproduce

  1. iOS 9.2.1
  2. iPhone 6
  3. RN 0.18
frostney commented 8 years ago

There is an issue with iOS 9 compatibility right now and I'm not sure that this might be a side effect. Let me see if I can reproduce the issue on my end and get back to you in a bit.

stu60610 commented 8 years ago

In iOS 9, there is a newly added property called "allowsBackgroundLocationUpdates" in CLLocationManager class. And it's default vaule is "NO".

So if we want to range beacon in background mode, we have to set this property to "YES", to achieve this, edit the RNBeacon.m file and add these lines below:

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
    [self.locationManager requestAlwaysAuthorization];
}
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9) {
    self.locationManager.allowsBackgroundLocationUpdates = YES;
}
[self.locationManager startUpdatingLocation];

It should be work fine in iOS 9.

frostney commented 8 years ago

@stu60610 Thank you for very much. I'll package that up it into a release very soon.

stu60610 commented 8 years ago

@frostney You're welcome. This is a great module and it helps me a lot!

BTW, If you need more information, here's some links I found to handle this problem:

http://stackoverflow.com/questions/30808192/allowsbackgroundlocationupdates-in-cllocationmanager-in-ios9

https://developer.apple.com/videos/play/wwdc2015/714/

albo1337 commented 8 years ago

@stu60610 Thank you very much! This works like a charm

felixaa commented 7 years ago

Does anyone of you guys have some sample code on how you handle background scans:

if ([launchOptions objectForKey:@"UIApplicationLaunchOptionsLocationKey"]) { NSLog(@"DID RECIEVE REGION"); }

Like local notifications or just opening the app

@marseca Can you confirm that @stu60610 suggestion fixed you'r issue. I'm kinda confused on how to log/handle scans in background

stuarttayler commented 7 years ago

I would also like to know how to handles background scans like @felixaa. In the README.md file there's the following snippet:

 // a region we were scanning for has appeared, ask to open us
  if([launchOptions objectForKey:@"UIApplicationLaunchOptionsLocationKey"])
  {
    //pop a notification to ask user to open, or maybe reload your scanner with delegate so that code fires
  }

I'm assuming this goes in the AppDelegate.m file, so just wondering @frostney or anyone has more details on popping a notification and/or reloading the scanner? Does this have to be done in the appdelegate file in Swift or can I use the react-native notification library?

felixaa commented 7 years ago

If you want to pop a local notification you can do this.

  if ([launchOptions objectForKey:@"UIApplicationLaunchOptionsLocationKey"]) {
    NSLog(@"DID RECIEVE REGION");
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    notification.alertBody = @"SAH DUDE?!";
    notification.soundName = UILocalNotificationDefaultSoundName;

    [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
  }

Btw is it event possible for the JS and 'beaconDidRange' to run in background?

stuarttayler commented 7 years ago

Thanks @felixaa, that's really helpful.

I guess at that point there's no way to add details about the location event? I was reading on the apple dev site about how you would need to create a CLLocationManager object to get info about the location data. But by adding in more native code it seems to defeat the point of using this library?

That's why I'm wondering if there's a way to run the JS code from the ([launchOptions objectForKey:@"UIApplicationLaunchOptionsLocationKey"]) delegate. Not sure if that's what is implied with the "reload your scanner with delegate so that code fires" comment in the code?

felixaa commented 7 years ago

I've kinda solved this issue(I think)

You'r AppDelegate.m needs a CLLocationManagerDelegate:

@interface AppDelegate() <CLLocationManagerDelegate>

@property (strong, nonatomic) CLLocationManager *locationManager;

@end

Then in application didFinishLaunchingWithOptions method you need to do this:

  if ([launchOptions objectForKey:@"UIApplicationLaunchOptionsLocationKey"]) {
    NSLog(@"DID RECIEVE REGION");

    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;

    [self.locationManager requestAlwaysAuthorization];

    NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString:@"YOUR UUID"];

     CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:beaconUUID identifier:@"YOUR IDENTIFIER"];

    [self.locationManager startMonitoringForRegion:beaconRegion];
  }

Then you can override didEnter/didExit region event-handlers:

-(void)locationManager:(CLLocationManager *)manager
        didEnterRegion:(CLBeaconRegion *)region {

  UILocalNotification *notification = [[UILocalNotification alloc] init];
  NSString *alertBody = [@"You've entered region: " stringByAppendingString:region.identifier];
  notification.alertBody = alertBody;
  notification.soundName = UILocalNotificationDefaultSoundName;

  [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}

-(void)locationManager:(CLLocationManager *)manager
         didExitRegion:(CLBeaconRegion *)region {

  UILocalNotification *notification = [[UILocalNotification alloc] init];
  NSString *alertBody = [@"You've exited region: " stringByAppendingString:region.identifier];
  notification.alertBody = alertBody;
  notification.soundName = UILocalNotificationDefaultSoundName;

  [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
jonathanroze commented 7 years ago

When i add

 // a region we were scanning for has appeared, ask to open us
  if([launchOptions objectForKey:@"UIApplicationLaunchOptionsLocationKey"])
  {
    //pop a notification to ask user to open, or maybe reload your scanner with delegate so that code fires
  }

And use NSLOG nothing happen :( ! Do you have an idea?

relaxedtomato commented 7 years ago

@Clowning - I just tried the same thing, did you make any progress on this?

felixaa commented 7 years ago

@Clowning @r-bansal Look at my last comment