I created a small application based on ibeacons and location.
I am calling a web service to download some information.
When I call the function "downloadDataFromServerWithMajor" from didFinishLaunchingWithOptions its works ok.
The problem comes when the app is on background and the "startMonitoringSignificantLocationChanges" calls "didUpdateLocations" callback. The "fetchNetworkFromMajor" from Manager.m is firing, and i get the two logs:
NSLog(#"fetchNetworkFromMajor -->PATH:%#", path);
NSLog(#"fetchNetworkFromMajor -->major:%# -->version:%ld -->language:%#", major, (long)version, language);
but since then nothing happens, no response object or error on get function.
[self GET:path parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSMutableArray *networks = [[NSMutableArray alloc] initWithCapacity:[responseObject count]];
NSLog(#"responseObject: %#", responseObject );
Network *net = [[Network alloc] initWithJSONDictionary:responseObject];
if (net) {
NSLog(#"NET:%#", net);
[networks addObject:net];
}
successHandler(networks);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"error manager:%#", error);
errorHandler(error);
}];
My code (from AppDelegate.m and Manager.m) is below:
AppDelegate.m
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self downloadDataFromServerWithMajor:#"1" andVersion:1];
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
id locationValue = [launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey];
[self.locationManager startMonitoringSignificantLocationChanges];
NSString *message =[NSString stringWithFormat:#"UIApplicationLaunchOptionsLocationKey %#",locationValue];
[self sendLocalNotificationWithMessage:message];
}
if ([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]) {
NSLog(#"RESPONDS!!!!!");
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound|UIUserNotificationTypeBadge categories:nil]];
}
// Override point for customization after application launch.
NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString:#"B9407F30-F5F8-466E-AFF9-25556B57FE6A"];
NSString *regionIdentifier = #"iBeacons region 1";
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID: beaconUUID identifier: regionIdentifier ];
beaconRegion.notifyEntryStateOnDisplay = YES;
self.locationManager = [[CLLocationManager alloc]init];
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]){
[self.locationManager requestAlwaysAuthorization];
}
self.locationManager.delegate = self;
[self.locationManager startMonitoringForRegion:beaconRegion];
return YES;
}
- (void) locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region{
if(beacons.count > 0){
for (CLBeacon *beacon in beacons){
NSLog(#"beacon detectado major: %# minor: %#", beacon.major,beacon.minor);
}
}
}
- (void) locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
[manager startRangingBeaconsInRegion:(CLBeaconRegion*) region];
NSLog(#"didEnterRegion --> has entrado en la region");
}
- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*) region];
NSLog(#"didExitRegion --> has salido de la region");
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
if (state == CLRegionStateInside) {
//Start Ranging
[manager startRangingBeaconsInRegion:(CLBeaconRegion*) region];
NSLog(#"didDetermineState -->has entrado en la region");
}else{
//Stop Ranging
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*) region];
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
//[self detectBluetooth];
NSString *message = #"didUpdateLocations";
[self sendLocalNotificationWithMessage:message];
[self downloadDataFromServerWithMajor:#"1" andVersion:1];
}
-(void)sendLocalNotificationWithMessage:(NSString*)message {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = message;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSString *message =#"applicationDidEnterBackground";
[self sendLocalNotificationWithMessage:message];
[self.locationManager startMonitoringSignificantLocationChanges];
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void) downloadDataFromServerWithMajor: (NSString *) major andVersion: (NSInteger) version
{
//downloadDataFromServer
NSString * language = [[NSLocale preferredLanguages] objectAtIndex:0];
NSLog(#"downloadDataFromServerWithMajor -->major: %# -->version:%ld", major,(long)version);
[[MyFacade sharedInstance] fetchNetworkFromMajor: major andVersion: version andLanguage: language withDelegate:self errorHandler:self];
}
-(void)didEndFetchingNetwork:(NSArray *)network{
NSLog(#"didEndFetchingNetwork");
}
-(void)networkFetchingEror:(NSError *)error
{
NSLog(#"error networkFetchingEror");
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSString *message =#"applicationDidBecomeActive";
[self sendLocalNotificationWithMessage:message];
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
Manager.m
-(void)fetchNetworkFromMajor:(NSString *)major andVersion:(NSInteger )version andLanguage: (NSString *) language
successHandler:(void (^)(NSArray *pois))successHandler
errorHandler:(void (^)(NSError *error))errorHandler {
NSString *path = [NSString stringWithFormat:ManagerFetchNetworkRelativeURLFormat,
major,(long)version, language];
NSLog(#"fetchNetworkFromMajor -->PATH:%#", path);
NSLog(#"fetchNetworkFromMajor -->major:%# -->version:%ld -->language:%#", major, (long)version, language);
[self GET:path parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSMutableArray *networks = [[NSMutableArray alloc] initWithCapacity:[responseObject count]];
NSLog(#"responseObject: %#", responseObject );
Network *net = [[Network alloc] initWithJSONDictionary:responseObject];
if (net) {
NSLog(#"NET:%#", net);
[networks addObject:net];
}
successHandler(networks);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"error manager:%#", error);
errorHandler(error);
}];
}
I dont understand why the same call works correctly when run from "didFinishLaunchingWithOptions" and when run from "didUpdateLocations" nothing happens.
Any ideas? I'm really frustrated with this issue.
add initialization of Manager
+ (instancetype)sharedInstance
{
static MyManager *_sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Network activity indicator manager setup
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
// Session configuration setup
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = #{
#"User-Agent" : #"iOS Client"
};
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:10 * 1024 * 1024 // 10MB. memory cache
diskCapacity:50 * 1024 * 1024 // 50MB. on disk cache
diskPath:nil];
sessionConfiguration.URLCache = cache;
sessionConfiguration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
//sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
// Initialize the session
_sharedInstance = [[MyManager alloc] initWithBaseURL:[NSURL URLWithString:MyManagerBaseURL] sessionConfiguration:sessionConfiguration];
});
return _sharedInstance;
}
EDIT: I added the call [self downloadDataFromServerWithMajor:#"1" andVersion:1]; to the method didRangeBeacons: and when the app detcts a ibeacon on background the call works ok too.
In order to download in the background your NSURLSessionConfiguration has to be initialized as a background session.
Apple Documentation for background downloads
Try this instead in your Manager initialization
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:#"bgDownload"];
Related
After receive a push notification I am trying to get the current localization.
However when I start the code bellow the corelocation fails (didFailWithError) when the app is at background :(
Only works in foreground.
Why?
I already did the .plist for background configuration
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler{
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
NSLog(#" Finalizado background...");
}];
[self handleStartMonitoringLocation];
NSLog(#"Main Thread proceeding...");
}
-(void)handleStartMonitoringLocation{
// location
if (!self.shareModel) {
self.shareModel = [LocationManager sharedManager];
self.shareModel.afterResume = NO;
[self.shareModel addApplicationStatusToPList:#"didFinishLaunchingWithOptions"];
}
[self.shareModel addApplicationStatusToPList:#"applicationDidBecomeActive"];
//Remove the "afterResume" Flag after the app is active again.
self.shareModel.afterResume = NO;
// [self.shareModel startMonitoringLocation];
[self.shareModel startBackgroundLocationUpdates];
}
-(void)startBackgroundLocationUpdates {
// Create a location manager object
self.anotherLocationManager = [[CLLocationManager alloc] init];
// Set the delegate
self.anotherLocationManager.delegate = self;
// Request location authorization
[self.anotherLocationManager requestWhenInUseAuthorization];
// Set an accuracy level. The higher, the better for energy.
self.anotherLocationManager.desiredAccuracy = kCLLocationAccuracyBest;
// Enable automatic pausing
self.anotherLocationManager.pausesLocationUpdatesAutomatically = YES;
// Specify the type of activity your app is currently performing
self.anotherLocationManager.activityType = CLActivityTypeFitness;
if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"9.0")){
// Enable background location updates
self.anotherLocationManager.allowsBackgroundLocationUpdates = YES;
[self.anotherLocationManager requestLocation];
}
else{
// Start location updates
[self.anotherLocationManager startUpdatingLocation];
}
}
#pragma mark - CLLocationManager Delegate
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"locationManager error: %#",error);
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
NSLog(#"locationManager didUpdateLocations: %#",locations);
}
I have noticed that whenever I start again the app and look at the following method I get that the beaconRegion returned is not nill. How does this work precisely?
beaconRegion = [self.locationManager.monitoredRegions member:beaconRegion];
// for more context on how I use this please look at the code below
In other words, how does iOS handle the allocation of CLLocationManager? Does it deserialise it every time the app is woken up and in this way retrieve the region information?
This is the output of the Xcode debugger console when running the code below:
2015-11-11 09:44:13.718 RegionMonitoringTest[239:15121] AppDelegate: creating new location manager object
2015-11-11 09:44:13.722 RegionMonitoringTest[239:15121] BeaconMonitoring class: in startRangingForBeacons, startupdatinglocation, range for my beacon
2015-11-11 09:44:13.724 RegionMonitoringTest[239:15121] Region already in list
2015-11-11 09:44:13.732 RegionMonitoringTest[239:15121] AppDelegate: Application did became active.
2015-11-11 09:44:13.762 RegionMonitoringTest[239:15121] BeaconMonitoring -> LocationManager: didFailWithError | Error: Error Domain=kCLErrorDomain Code=0 "(null)"
2015-11-11 09:44:13.762 RegionMonitoringTest[239:15121] Requesting to start ranging for beacons again.
2015-11-11 09:44:13.762 RegionMonitoringTest[239:15121] BeaconMonitoring class: in startRangingForBeacons, startupdatinglocation, range for my beacon
2015-11-11 09:44:13.767 RegionMonitoringTest[239:15121] Region already in list
Below I paste the source code that I am using to test this, it may be helpful (it is based on the AirLocate example provided by Apple):
#import "AppDelegate.h"
#define BEACON_REGION #"01020102-0102-0102-0102-010201020102"
#interface AppDelegate ()
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (strong, nonatomic) NSMutableArray * monitoredRegions;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
NSLog(#"AppDelegate: being woken up after entering region");
}
else{
NSLog(#"AppDelegate: creating new location manager object");
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.pausesLocationUpdatesAutomatically = false;
self.locationManager.allowsBackgroundLocationUpdates = true;
self.locationManager.delegate = self;
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
self.monitoredRegions = [[NSMutableArray alloc] initWithCapacity:10];
[self startRangingForBeacons];
}
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
NSLog(#"AppDelegate: will resign active");
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog(#"AppDelegate: did enter background");
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
//[self.bluetoothDataSync stopScanning];
NSLog(#"AppDelegate: did enter foreground");
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
NSLog(#"AppDelegate: Application did became active.");
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
//[[UIApplication sharedApplication] cancelAllLocalNotifications];
NSLog(#"AppDelegate: App will terminate");
}
//////////////////////////////////////////////////
- (void) startRangingForBeacons{
NSLog(#"BeaconMonitoring class: in startRangingForBeacons, startupdatinglocation, range for my beacon");
[self startMonitoringForRegion:[[NSUUID alloc] initWithUUIDString:BEACON_REGION] :#"my-beaconregion"];
}
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSString * message = [NSString stringWithFormat:#"BeaconMonitoring -> LocationManager: monitoringDidFailForRegion | Error: %#, Region identifier: %#", error, region.identifier];
NSLog(#"%#", message);
NSLog(#"Requesting to start ranging for beacons again.");
[self startRangingForBeacons];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSString * message = [NSString stringWithFormat:#"BeaconMonitoring -> LocationManager: didFailWithError | Error: %#", error];
NSLog(#"%#", message);
NSLog(#"Requesting to start ranging for beacons again.");
[self startRangingForBeacons];
}
- (void) startMonitoringForRegion:(NSUUID*)beaconUUID :(NSString*)regionIdentifier{
CLBeaconRegion *beaconRegion = nil;
beaconRegion = [self.locationManager.monitoredRegions member:beaconRegion];
if(beaconRegion)
{
NSLog(#"Region already in list");
}
else{
beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:beaconUUID identifier:regionIdentifier];
beaconRegion.notifyEntryStateOnDisplay = YES;
beaconRegion.notifyOnEntry = YES;
beaconRegion.notifyOnExit = YES;
[self.locationManager startMonitoringForRegion:beaconRegion];
}
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
[self.locationManager startUpdatingLocation];
[self.monitoredRegions addObject:beaconRegion];
}
- (void) stopRangingForbeacons{
NSLog(#"BeaconMonitoring: stopRangingForbeacons - Stops updating location");
[self.locationManager stopUpdatingLocation];
for (int i=0; i < [self.monitoredRegions count]; i++) {
NSObject * object = [self.monitoredRegions objectAtIndex:i];
if ([object isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion * region = (CLBeaconRegion*)object;
[self.locationManager stopMonitoringForRegion:region];
[self.locationManager stopRangingBeaconsInRegion:region];
}
else{
NSLog(#"BeaconMonitoring: unrecongized object in beacon region list");
}
}
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
if (![CLLocationManager locationServicesEnabled]) {
NSLog(#"Couldn't turn on ranging: Location services are not enabled.");
}
if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorized) {
NSLog(#"Couldn't turn on monitoring: Location services not authorised.");
}
}
#pragma CLLocationManagerDelegate
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"BeaconMonitoring: did enter region, will now start ranging beacons in this region");
[manager startRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"BeaconMonitoring: did exit region, will now: stop ranging beacons in this region; stop updating locations; stop scanning for BLE");
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager stopUpdatingLocation];
NSDictionary * notificationData = #{ #"value" : #"exitedRegion"};
[[NSNotificationCenter defaultCenter] postNotificationName:#"dataUpdate" object:nil userInfo:notificationData];
}
- (void) locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSString * message = #"CLRegionState: ";
switch (state) {
case CLRegionStateInside:
message = #"CLRegionState: state inside";
break;
case CLRegionStateOutside:
message = #"CLRegionState: state outside";
break;
case CLRegionStateUnknown:
message = #"CLRegionState: state unknown";
break;
default:
message = #"CLRegionState: default case";
break;
}
NSLog(#"%#", message);
}
-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
bool rangedBeacons = false;
if ([beacons count]>0) {
for (int i=0; i<[beacons count]; i++) {
CLBeacon *beacon = [beacons objectAtIndex:i];
if((beacon.major.intValue == 4) && (beacon.major.intValue == 8)){
rangedBeacons = true;
}
}
}
if (rangedBeacons) {
NSLog(#"Region identifier: %#", region.identifier);
NSString * message = [NSString stringWithFormat:#"BeaconMonitoring: ranged a total of %lu, hence request scan start", (unsigned long)[beacons count]];
NSLog(#"%#", message);
}
}
Since Location Manager's monitoring works even when the app is not running, e.g., was terminated due to memory pressure or killed by the user at the app switcher, iOS needs to keep the list of regions apps monitor for somewhere outside the app, and you can think of monitoredRegions as the reflection of that list—rather than an instance property that vanishes when the object gets deallocated.
Note for example that if you instantiate more than one location manager, they all share the same monitoring list—a direct outcome of the list being stored per-app somewhere on the OS level. If you start/stop monitoring for a region in one location manager, it'll affect all the others.
All of this applies to CLBeaconRegion, but also to "regular" CLCircularRegion—because it's a feature of monitoring, and not something beacon-specific.
I built a simple ios app with IBeacon, when the app is in foreground or background it works ok but after rebooting my phone, my app stops getting CoreLocation delegate callbacks. This is my AppDelegate.m code.
#import "AppDelegate.h"
#import <CoreLocation/CoreLocation.h>
#import "ViewController.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]) {
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound
categories:nil]];
}
if(launchOptions != nil){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"alerta"
message:[launchOptions[UIApplicationLaunchOptionsLocalNotificationKey] description]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString:#"B39ED98FF-2900-441A-802F-9C398FC199D2"];
NSString *regionIdentifier = #"iBeacons region 1";
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID: beaconUUID identifier: regionIdentifier ];
beaconRegion.notifyEntryStateOnDisplay = YES;
self.locationManager = [[CLLocationManager alloc]init];
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]){
[self.locationManager requestAlwaysAuthorization];
}
self.locationManager.delegate = self;
//self.locationManager.pausesLocationUpdatesAutomatically = NO;
[self.locationManager startMonitoringForRegion:beaconRegion];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
[self.locationManager startUpdatingLocation];
return YES;
}
-(void)sendLocalNotificationWithMessage:(NSString*)message {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = message;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
- (void) locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region{
ViewController *viewController = (ViewController*) self.window.rootViewController;
viewController.beacons = beacons;
[viewController.tableView reloadData];
NSString *message = #"";
if(beacons.count > 0){
CLBeacon *nearestBeacon = beacons.firstObject;
if(nearestBeacon.proximity == self.lastProximity || nearestBeacon.proximity == CLProximityUnknown){
return;
}
self.lastProximity = nearestBeacon.proximity;
switch (nearestBeacon.proximity) {
case CLProximityFar:
message = #"CLProximityFar";
break;
case CLProximityNear:
message= #"CLProximityNear";
break;
case CLProximityImmediate:
message= #"CLProximityImmediate";
break;
case CLProximityUnknown:
return;
}
}else {
message = #"No BEACONS";
}
NSLog(#"%#", message);
[self sendLocalNotificationWithMessage:message];
}
- (void) locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
[manager startRangingBeaconsInRegion:(CLBeaconRegion*) region];
[self.locationManager startUpdatingLocation];
NSLog(#"INSIDE REGION");
[self sendLocalNotificationWithMessage:#"INSIDE REGION"];
}
- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*) region];
[self.locationManager stopUpdatingLocation];
NSLog(#"OUTSIDE REGION");
[self sendLocalNotificationWithMessage:#"OUTSIDE REGION"];
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
if (state == CLRegionStateInside) {
//Start Ranging
[manager startRangingBeaconsInRegion:(CLBeaconRegion*) region];
[self.locationManager startUpdatingLocation];
NSLog(#"INSIDE REGION");
[self sendLocalNotificationWithMessage:#"INSIDE REGION"];
}
else{
//Stop Ranging
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*) region];
[self.locationManager stopUpdatingLocation];
NSLog(#"OUTSIDE REGION");
[self sendLocalNotificationWithMessage:#"OUTSIDE REGION"];
}
}
-(void)application:(UIApplication *)application didReceiveLocalNotification:(NSDictionary *)userInfo {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"ALERT"
message:#"didReceiveLocalNotification"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
I cant find what is the issue, i need your help!
The code looks OK. When testing this, I would recommend you do the following:
Launch your app
Turn on your beacon
Verify you get a didEnterRegion callback.
Turn off your beacon
Verify you get a didExitRegion callback.
Reboot your phone
Wait at least 2 minutes (it takes a bit of time before CoreLocation fully starts up after reboot.)
Turn on your beacon
Wait a few minutes to see if you get a didEnterRegion callback.
In my app I want to fetch and upload location on server in regular intervals. for foreground it works fine but for background it has issue as described below. One thing to mention here I use different class for foreground and separate code for background.
To fetch location in background I do the following. It does upload location for about 24 - 40 hours but get stopped after 24-40 hours. I did not get "willTerminate" event. I have also written code to capture global execption so that I could log SigKill if user kill my app but it too does not get logged reason could be we can't catch SigKill.
Now I am stuck don't know what to do to keep my app alive in background to upload location in regular intervals.
//////////////////////////////// didfinish lauch ///////////////////////////////
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
return YES;
}
////////// **did enter background**
- (void) applicationDidEnterBackground:(UIApplication *)application
{
self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}];
timer = [[NSTimer scheduledTimerWithTimeInterval:8*60
target:self
selector:#selector(changeAccuracy)
userInfo:nil
repeats:YES] retain];
[self.locationManager startUpdatingLocation];
}
- (void) changeAccuracy
{
self.bCallbackStarted = NO;
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[self.locationManager setDistanceFilter:100];
}
///////// **did enter fore ground**
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[self.locationManager stopUpdatingLocation];
}
////////// **did update location**
-(void)locationManager:(CLLocationManager *)lm didUpdateLocations:(NSArray *)locations
{
if (self.bCallbackStarted == YES) {
NSString* logStr = [NSString stringWithFormat:#"URLFilteringAppDelegate::didUpdateLocations not uploading location because self.bCallbackStarted == YES"];
LOGI(logStr)
return;
}
self.bCallbackStarted = YES;
[[AppUtility sharedInstance] setLocationStatus:YES];
CLLocation *location = [locations lastObject];
if(location.coordinate.latitude == 0 && location.coordinate.longitude == 0)
{
[UserSettings sharedInstance].Latitude = [NSString stringWithFormat:#"%#", #"Unavailable"];
[UserSettings sharedInstance].Longitude = [NSString stringWithFormat:#"%#", #"Unavailable"];
}
else
{
[UserSettings sharedInstance].Latitude = [NSString stringWithFormat:#"%.8f", location.coordinate.latitude];
[UserSettings sharedInstance].Longitude = [NSString stringWithFormat:#"%.8f", location.coordinate.longitude];
}
[[LocationManager sharedInstance] sendLocation];
/// this is set to save battery
[lm setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
[lm setDistanceFilter:99999];
}
What I am trying to achieve is described in the following steps
1.User opens the app, navigates blah blah blah
2.User presses the home button, the app shuts itself
3.A background task for detecting CLbeacons starts. On every detection, A UILocalNotification is fired immediately, so as to make the user aware that a beacon has been discovered.
Now what happens with me
I open the app, navigate then press the home button to shut the app
The phone slowly darkens itself, i.e it goes to the slide to unlock state. Here no notification gets fired; Hell, the CLLocationManger's method -(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region doesn't get called at all.
The phone goes to sleep. I slide open the phone to awake it; Immediately multiple `UIlocalNotifications enter.
Can anyone explain to me why is this happening?
//
// AppDelegate.m
// Beacon Proximity
//
// Created by Debanjan Chakraborty on 7/15/14.
// Copyright (c) 2014 Debanjan Chakraborty. All rights reserved.
//
#import "AppDelegate.h"
#import "CustomerRegViewController.h"
#import "IntialViewController.h"
#import "BeaconProduct.h"
#import "DBManager.h"
#import "APLDefaults.h"
#import "ScanViewController.h"
typedef NS_ENUM(NSUInteger, BeaconType)
{
Check,
Bundle
};
#interface AppDelegate ()<CLLocationManagerDelegate>
#property CLLocationManager *locationAppManager;
#property (nonatomic) NSInteger localNotificationCount;
//#property (nonatomic,strong) UILocalNotification *localNotification;
//#property (nonatomic) NSTimer *timerForNotifier;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"HasLaunchedOnce"])
{
// app already launched
}
else
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"HasLaunchedOnce"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self entryDatabase];
}
[[UIApplication sharedApplication] cancelAllLocalNotifications];
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (notification)
{
NSLog(#" aache ?");
// self.localNotificationCount = 0;
UIAlertView *aw = [[UIAlertView alloc] initWithTitle:#"Case 1" message: notification.alertBody delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[aw show];
}
application.applicationIconBadgeNumber = 0;
return YES;
}
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"<>"]];
token = [token stringByReplacingOccurrencesOfString:#" " withString:#""];
NSLog(#"content---%#", token);
}
- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSLog(#"Error %#",err.localizedDescription);
}
- (void)applicationWillResignActive:(UIApplication *)application
{
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"HasLaunchedOnce"])
{
NSLog(#" Will REsign and initialise");
}
else
{
NSLog(#" Will REsign?");
}
}
-(void)applicationWillTerminate:(UIApplication *)application
{
NSLog(#" terminated");
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[UIApplication sharedApplication] cancelAllLocalNotifications];
[self initialise];
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
application.applicationIconBadgeNumber=0;
for (NSUUID *uuid in [APLDefaults sharedDefaults].supportedProximityUUIDs)
{
CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:[uuid UUIDString]];
[self.locationAppManager stopMonitoringForRegion:region];
}
self.locationAppManager = nil;
}
-(void)insert:(NSDate *)fire WithBeaconType:(BeaconType )beaconType ForBeacon:(CLBeacon *)beacon
{
self.localNotificationCount++;
UILocalNotification *localNotification = [UILocalNotification new];
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.applicationIconBadgeNumber =self.localNotificationCount;
localNotification.fireDate = [NSDate date];
localNotification.alertAction = #"Show me";
if(beaconType == Check)
{
localNotification.alertBody = [NSString stringWithFormat:#"Check out new offers"];
localNotification.alertAction = NSLocalizedString(#"Read Offer", nil);
}
else
{
localNotification.alertBody = [NSString stringWithFormat:#"You've reached offers. Grab them before they expire soon"];
localNotification.alertAction = NSLocalizedString(#"Grab Offer", nil);
}
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
NSLog(#" should Notify minor is %d and major is %d",[beacon.minor integerValue],[beacon.major integerValue]);
[[DBManager getSharedInstance] updateNotifiedTimeForBeacon:beacon];
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
self.localNotificationCount = 0;
[[UIApplication sharedApplication] cancelAllLocalNotifications];
}
-(UIViewController *)omegaScreen
{
UIStoryboard *strybrd = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
NSString *viewId=[[NSUserDefaults standardUserDefaults]objectForKey:#"custId"]?#"initial":#"custReg";
UIViewController *controller=[strybrd instantiateViewControllerWithIdentifier:viewId];
if([[NSUserDefaults standardUserDefaults] objectForKey:#"custId"])
[self initialise];
return [viewId isEqualToString:#"initial"]?(IntialViewController *)controller:(CustomerRegViewController *)controller;
}
-(void)initialise
{
self.locationAppManager = [[CLLocationManager alloc] init];
self.locationAppManager.delegate = self;
for (NSUUID *uuid in [APLDefaults sharedDefaults].supportedProximityUUIDs)
{
NSLog(#" uuid is %#",uuid.UUIDString);
CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:[uuid UUIDString]];
region.notifyEntryStateOnDisplay=YES;
[self.locationAppManager startMonitoringForRegion:region];
}
self.localNotificationCount = -1;
}
- (void) locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
NSLog(#" it did");
if([region isKindOfClass:[CLBeaconRegion class]])
{
[self.locationAppManager requestStateForRegion:(CLBeaconRegion*)region];
}
}
-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if (state == CLRegionStateInside)
{
//Start Ranging
[manager startRangingBeaconsInRegion:(CLBeaconRegion*)region];
}
}
- (void)locationManager:(CLLocationManager*)manager didEnterRegion:(CLRegion *)region
{
if ([region isKindOfClass:[CLBeaconRegion class]])
{
[self.locationAppManager startRangingBeaconsInRegion:(CLBeaconRegion*)region];
}
}
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
// if([self runningInBackground])
// {
// NSLog(#" didRangeBeacons in App Delegate");
for (CLBeacon *beacon in beacons)
{
if([[DBManager getSharedInstance]shouldNotifyFor:beacon])
{
BeaconType type;
if([beacon.major integerValue]==0)
{
type = Check;
}
else
{
type = Bundle;
}
[self insert:[NSDate date]WithBeaconType:type ForBeacon:beacon];
}
}
// }
}
-(BOOL) runningInBackground
{
UIApplicationState state = [UIApplication sharedApplication].applicationState;
BOOL result = (state == UIApplicationStateBackground);
return result;
}
-(void)resetStatus
{
NSLog(#" timer has been fired");
}
-(void)entryDatabase
{
NSMutableArray *localArray = [NSMutableArray new];
BeaconProduct *bP1 = [[BeaconProduct alloc] initWithProductSku:#"1" andId:#"1" andbTitle:#"BeaconAdTitle1" andDescription:#"Description1" andCurrency:#"Rupees" andSmallImage:#"Shampoo" andLargeImage:#"Shampoo" andPrice:[NSNumber numberWithInteger:700]];
BeaconProduct *bP2 = [[BeaconProduct alloc] initWithProductSku:#"2" andId:#"1" andbTitle:#"BeaconAdTitle2" andDescription:#"Description2" andCurrency:#"Rupees" andSmallImage:#"conditionerFree" andLargeImage:#"conditionerFree" andPrice:[NSNumber numberWithInteger:200]];
BeaconProduct *bP3 = [[BeaconProduct alloc] initWithProductSku:#"3" andId:#"2" andbTitle:#"BeaconAdTitle3" andDescription:#"Description3" andCurrency:#"Rupees" andSmallImage:#"soap" andLargeImage:#"soap" andPrice:[NSNumber numberWithFloat:20.50]];
BeaconProduct *bP4 = [[BeaconProduct alloc] initWithProductSku:#"4" andId:#"3" andbTitle:#"BeaconAdTitle4" andDescription:#"Description4" andCurrency:#"Rupees" andSmallImage:#"shirt" andLargeImage:#"shirt" andPrice:[NSNumber numberWithFloat:250]];
[localArray addObject:bP1];
[localArray addObject:bP2];
[localArray addObject:bP3];
[localArray addObject:bP4];
[[DBManager getSharedInstance] insertBeaconProduct:localArray];
}
#end
You should post your code so we can explain this definitively, but it sounds like you simply have region.notifyEntryStateOnDisplay = YES; when setting up your region. When you have that option set, you will get an extra callback to your region entry callback from CLLocationManager every time you illuminate the display.
EDIT: After seeing the code, I also noticed that the app starts monitoring when it is about to to the background, then stops it when it goes back into the foreground. I suspect this may be complicating the issue and giving you extra callbacks causing extra notifications. It doesn't look like there is any reason to keep stopping and restarting monitoring. I would suggest you simply set up monitoring of your beacon regions when the app starts up, and leave monitoring going. This combined with setting region.notifyEntryStateOnDisplay=NO should prevent the multiple notifications.
SECOND EDIT: As to point (2), you should understand that you generally CANNOT range in the background at all. Once your app leaves the foreground, you can only get callbacks to didRangeBeacons: inRegion: for about five seconds, at which time such callbacks stop until the app is woken up again. One way you can wake it up is by transitioning from being in/out of a beacon region using the monitoring APIs. However, you should understand that in the background, these transitions are not always instantaneous and can take up to 15 minutes. See here for details.