Display background notification when CLProximityNear - ios7

I am trying to display a notification when my app is in the background and the device enters a region of an iBeacon and when their CLProximity is Near the notification is working, but it keeps appearing at 1 second intervals:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
NSLog(#"Entered beacon region");
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}
- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
NSLog(#"Left region");
[self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
}
- (void) locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}
- (void) locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
CLBeacon *beacon = [[CLBeacon alloc] init];
beacon = [beacons lastObject];
self.uuidLabel.text = beacon.proximityUUID.UUIDString;
if(beacon.proximity == CLProximityUnknown) {
distanceLabel.text = #"Unknown Proximity";
[self.view setBackgroundColor:[UIColor grayColor]];
} else if (beacon.proximity == CLProximityImmediate) {
distanceLabel.text = #"Immediate";
[self.view setBackgroundColor:[UIColor redColor]];
} else if (beacon.proximity == CLProximityNear) {
distanceLabel.text = #"Near";
[self.view setBackgroundColor:[UIColor orangeColor]];
UILocalNotification *inRange = [[UILocalNotification alloc] init];
inRange.alertBody = [NSString stringWithFormat:#"Entered region!"];
inRange.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] presentLocalNotificationNow:inRange];
} else if (beacon.proximity == CLProximityFar) {
distanceLabel.text = #"Far";
[self.view setBackgroundColor:[UIColor blueColor]];
}
}
Should there be a method call after the notification is displayed to tell the app that it has been displayed and not to keep calling the didRangeBeacons method until the user goes out of range and back in again?

When ranging beacons, locationManager:didRangeBeacons:inRegion will be called every second, as you've identified. Each time, the beacons parameter will contain an array of all visible beacons.
It's up to your app to contain logic to determine whether a new beacon is visible, or whether you've already notified the user about it. I suggest you store an array of previously discovered beacons, and each time locationManager:didRangeBeacons:inRegion is called you compare your list with the contents of the beacons parameter. You then should be able to tell whether any new beacons have been found.

The multiple notifications can be solved like this:
If you only want the notification to be sent once, simply define an alreadyDisplayed flag, which gets set after the notification is sent, then check its value before sending.
Like this:
BOOL alreadyDisplayed = NO;
...
- (void) locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
...
else if (beacon.proximity == CLProximityNear) {
distanceLabel.text = #"Near";
if (!alreadyDisplayed) {
[self.view setBackgroundColor:[UIColor orangeColor]];
alreadyDisplayed = YES;
UILocalNotification *inRange = [[UILocalNotification alloc] init];
inRange.alertBody = [NSString stringWithFormat:#"Entered region!"];
inRange.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] presentLocalNotificationNow:inRange];
}
}
...
}
But you still have a second problem:
If you want to do this in the background as your question title suggests, this isn't going to work at all. The problem is that after iOS detects that your phone entered an iBeacon region in the background, it only lets it run for five seconds before putting it to sleep. Because the range of an iBeacon is about 50 meters, the most likely case is that this five second interval will start when you are at the edge of that 50 meter range. It is very unlikely that the user will walk so fast that they enter the "Near" proximity within the 5 seconds you have before your app is put to sleep. For this reason, it is generally not possible to take specific action based upon an specific proximity when you are in the background.
That said, if you want to do this in the foreground, this will work just fine if you make changes to keep the notifications from coming every second.

Related

CLLocationManager updateLocation never called while app is in background

I've read so many question here at stackoverflow and I am still having issues with CLLocationManager.I have already added keys in info.plist (NSLocationAlwaysAndWhenInUseUsageDescription,NSLocationWhenInUseUsageDescription,NSLocationAlwaysAndWhenInUseUsageDescription). My app supports ios 9.0 to 11.x.
Update:- I'm testing on iphone6 ios 11.0.3 physical device
My Approach -
1. Start updating location after while using the app permission.
2. When app goes into background stop location manager to remove Blue Banner (Banner Of Shame)
3.Fire a periodic timer of 30 seconds and start location manager again.
This time I never got the delegate callback didUpdateLocation
I have a singleton class called LocationManager.
Here is my code from LocationManager and AppDelegate
LocationManager
- (void)startLocatingUser {
//Locate User
_locationMeasurements = [NSMutableArray array];
self.geocoder = [[CLGeocoder alloc] init];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.pausesLocationUpdatesAutomatically = NO;
self.locationManager.activityType = CLActivityTypeAutomotiveNavigation;
if ([self.locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
if(IS_OS_8_OR_LATER) {
if ([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
}
if (#available(iOS 11.0, *)) {
self.locationManager.showsBackgroundLocationIndicator = NO;
}
[self.locationManager startUpdatingLocation];
}
- (void)stopLocatingUser {
if(self.locationManager) {
[self.locationManager stopUpdatingLocation];
}
}
AppDelegateCode
- (void)applicationWillResignActive:(UIApplication *)application {
_isBackgroundMode = YES;
}
- (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.
LocationManager* locationManager = [LocationManager sharedLocationManager];
[locationManager stopLocatingUser];
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
self.bgTimer = [NSTimer scheduledTimerWithTimeInterval:30.0
target:self
selector:#selector(startTrackingBg)
userInfo:nil
repeats:YES];
}
-(void)startTrackingBg {
dispatch_async(dispatch_get_main_queue(), ^{
LocationManager* locationManager = [LocationManager sharedLocationManager];
[locationManager startLocatingUser];
});
NSLog(#"App is running in background");
}
I am never getting this delegate callback in background once I stop and start location manager again.
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations
What I simply want is whenever user puts the app in background. I want to hide the banner of shame and then I need periodic location updates in background and send them to server.

Unable to detect iBeacons in iOS9. Keep getting CLRegionStateOutside

I bought a few iBeacons and I followed a tutorial to try to discover them (http://ibeaconmodules.us/blogs/news/14279747-tutorial-ibeacon-app-development-with-corelocation-on-apple-ios-7-8).
It doesn't work.
locationManager:(CLLocationManager *)manager didRangeBeacons will always return an empty array.
If I tweak the code slightly, according to answer of this question(locationManager:didEnterRegion not called when a beacon is detected), I will keep getting CLRegionStateOutside
One thing to take note is that I found the UDID of my beacon my downloading a 3rd party app Light Blue. And that Light Blue app can correct recognises all my beacons.
Another thing to take note is that I have no idea what should I put in the identifier field so I just typed a string that is pasted outside the physical beacon.
This is the code:
- (void) locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
NSLog(#"did start monitoring");
[self.locationManager requestStateForRegion:self.beaconRegion];
}
-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if (state == CLRegionStateInside)
{
//Start Ranging
NSLog(#"inside, start ranging");
[manager startRangingBeaconsInRegion:self.beaconRegion];
}
else
{
NSLog(#"outside, stop ranging");
//Stop Ranging here
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
NSUUID *beaconUUID = [[NSUUID alloc] initWithUUIDString:#"9B2D1BB8-25AA-8EE5-2513-7C140B6B1801"];
NSString *regionIdentifier = #"MiniBeacon_04193";
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:beaconUUID
major:0 minor:0 identifier:regionIdentifier];
self.beaconRegion = beaconRegion;
self.beaconRegion.notifyOnEntry=YES;
self.beaconRegion.notifyOnExit=YES;
self.beaconRegion.notifyEntryStateOnDisplay=YES;
switch ([CLLocationManager authorizationStatus]) {
case kCLAuthorizationStatusAuthorizedAlways:
NSLog(#"Authorized Always");
break;
case kCLAuthorizationStatusAuthorizedWhenInUse:
NSLog(#"Authorized when in use");
break;
case kCLAuthorizationStatusDenied:
NSLog(#"Denied");
break;
case kCLAuthorizationStatusNotDetermined:
NSLog(#"Not determined");
break;
case kCLAuthorizationStatusRestricted:
NSLog(#"Restricted");
break;
default:
break;
}
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];
return YES;
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
[manager startRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager startUpdatingLocation];
NSLog(#"You entered the region.");
[self sendLocalNotificationWithMessage:#"You entered the region."];
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
[manager stopRangingBeaconsInRegion:(CLBeaconRegion*)region];
[self.locationManager stopUpdatingLocation];
NSLog(#"You exited the region.");
[self sendLocalNotificationWithMessage:#"You exited the region."];
}
-(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 {
NSString *message = #"";
IMViewController *viewController = (IMViewController*)self.window.rootViewController;
viewController.beacons = beacons;
[viewController.tableView reloadData];
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 = #"You are far away from the beacon";
break;
case CLProximityNear:
message = #"You are near the beacon";
break;
case CLProximityImmediate:
message = #"You are in the immediate proximity of the beacon";
break;
case CLProximityUnknown:
return;
}
} else {
message = #"No beacons are nearby";
}
NSLog(#"%#", message);
[self sendLocalNotificationWithMessage:message];
}
The problem is that the code is not looking for the proper ProximityUUID of the beacon. The 9B2D1BB8-25AA-8EE5-2513-7C140B6B1801 UUID in the code that originated from the Light Blue app is not a ProximityUUID needed to detect iBeacons.
Understand that there are many kinds of UUIDs. The Light Blue app will detect Bluetooth Service UUIDs but it will not detect iBeacon ProximityUUIDs. While the superficially look the same, they have different values and meanings. It may be that the Bluetooth Service UUID you scanned with Light Blue did originate from the beacon, but it still won't work in the code shown, because it isn't the correct ProximityUUID.
How do you figure out your beacon's ProximityUUID? A few options:
Ask the manufacturer, or use the manufacturer's configuration utility.
Use a dedicated beacon scanner for Android, OSX or Linux which lets you see any beacon regardless of the ProximityUUID. Unfortunately, iOS only lets you see beacons if you know the ProximityUUID up front.
Once you know the ProximityUUID, simply replace the value instead of the 9B2D1BB8-25AA-8EE5-2513-7C140B6B1801 shown.

Can we show user location in offline in MKMapview

Can we get current user location "That blue ball which is animating" when our device is offline.when i tried to get current user location and log it i'm getting 0.00000 for both longitude and latitude here is the code that i used to get current user location.I'm using ipad mini to test it.I have also added CLLocationManagerDelegate in .h file.
- (void)viewDidLoad {
[self getUserLocation];
self.myMapView.showsUserLocation = YES;
}
-(void)getUserLocation{
if ([CLLocationManager locationServicesEnabled]) {
locationManager = [[[CLLocationManager alloc] init] autorelease];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
[locationManager startUpdatingLocation];
}else{
NSLog(#"User location Disabled");
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
CLLocation *currentGPSLocation = newLocation;
if (currentGPSLocation != nil) {
currentLocation.latitude = self.myMapView.userLocation.coordinate.latitude;
currentLocation.longitude = self.myMapView.userLocation.coordinate.longitude;
currentLocationWalk.latitude=self.myMapView.userLocation.coordinate.latitude;
currentLocationWalk.longitude=self.myMapView.userLocation.coordinate.longitude;
NSLog(#"didUpdateToLocation: %f,%f", self.myMapView.userLocation.coordinate.latitude,self.myMapView.userLocation.coordinate.longitude);
statusLabel.textColor = [UIColor blackColor];
statusLabel.text = [NSString stringWithFormat:#"%f",self.myMapView.userLocation.coordinate.latitude];
statusLabel1.text = [NSString stringWithFormat:#"%f",self.myMapView.userLocation.coordinate.longitude];
}
}
You are not using ARC (I strongly suggest using it, it avoid most of the memory management errors!).
Therefore it might be that "locationManager" is a instance variable that is not retained (maybe you did not declare it as a retained property).
In this case, it might have been released already when your code returns to the main run loop, where the autorelease pool is drained.
This line
locationManager = [[[CLLocationManager alloc] init] autorelease];
is not a good idea.
define the locationManager as a property,
init the locationManager in viewDidLoad(), but remove the autorelease!
and relase the locationManager only when the viewController is unloaded (there where you release the other properties)

iOS App Background Location (Push Notifications)

I face an issue where my app requires the users location when they receive a push notification while the app is running in the background.
After doing a bit of reading I'm under the impression that this isn't possible, i.e:
1) App in background
2) Push notification received
3) Get users location and execute block of code.
I found this post on here that suggests you can run a task every N amount of minutes to get the users location. So using this I could check say every ten minutes which would work but it would be a waste of the users battery if I kept activating the GPS. Therefore I was wondering can I tell within my background task if the user has any push notification waiting for my app? That way I would check if they did every ten minutes and only activate the GPS if they did.
Perhaps I'm going about this all the wrong way, I would really appreciate any advice. Sorry for not having any of the code for the background task yet as I'm still trying to get it to work.
[UPDATE]
Thanks Dustin for your advice. After a bit of testing I've decided to go with an easier option. I am now using the Apple Significant Location Changes. I store the changes in an array, I've set this to only update if the last change was greater than 10min ago. Then when the app becomes active I match the closest time in the array to the time the push notification was sent and use that location.
As discussed I used alternative solution, here it is:
Under viewDidLoad or didFinishingLaunchingWithOptions add:
locationsLogged = [[NSMutableArray alloc] init];
lastLocationTime = [[NSDate alloc] init]
locationManager = [[CLLocationManager alloc] init];
[locationManager setDelegate:self];
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
Under applicationDidEnterBackground add:
[locationManager startMonitoringSignificantLocationChanges];
Add:
-(void) storeLocations:(CLLocation *)location
{
bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:
^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
[locationsLogged addObject:location];
if (bgTask != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
-(void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
BOOL isInBackground = NO;
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
{
isInBackground = YES;
}
NSDate *now = [NSDate date];
NSTimeInterval diff = [now timeIntervalSinceDate:lastLocationTime];
if ((int)diff > 600) {
if (isInBackground)
{
[lastLocationTime release];
lastLocationTime = [[NSDate alloc] init];
[self storeLocations:newLocation];
}
else
{
// ...
}
}
}
Then on applicationDidBecomeActive you can use whatever logic you need to apply location information.

UIAlertView showing up only after it's dismissed

I've been trying to figure this out for 2 days now, and before anyone posts another stackoverflow question, I've read them all and none of them cover my problem exactly:
I have a CoreData app that updates dynamically. Now during the update I want an UIAlertView to pop up saying that an update is being downloaded.
So here's the important code:
AppDelegate:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[myUpdater checkForUpdatesInContext:self.managedObjectContext];
}
_
Updater Class:
- (void)checkForUpdatesInContext:(NSManagedObjectContext *)myManagedObjectContext
{
[self loadUpdateTime];
NSLog(#"Update start");
NSDate *now = [NSDate dateWithTimeIntervalSinceNow:[[NSTimeZone localTimeZone] secondsFromGMT]];
if ([now timeIntervalSinceDate:updateTime] < UPDATE_TIME_INTERVAL)
{
return;
}
[self showAlertViewWithTitle:#"Update"];
... //updating process
[self.alertView dismissWithClickedButtonIndex:0 animated:YES];
NSLog (#"Update done");
}
- (void) showAlertViewWithTitle:(NSString *)title
{
self.alertView = [[UIAlertView alloc] initWithTitle:title message:#"Daten werden aktualisiert..." delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
... //design the alertView
[self.alertView show];
NSLog (#"AlertView shows");
}
So here is what happens when I run this:
Launch image shows
NSLog "Update starts" fires
NSLog "AlertView shows" fires
Screen dims but no AlertView is shown
Update is running
NSLog "Update done" fires
Launch image goes away and TabBarController shows up
UIAlertView shows up and is dismissed right away and the dimmed screen returns to normal
What I would like to have happen:
Launch image
TabBarController shows up
Screen dims and UIAlertView shows
Update is running
UIAlertView gets dismissed and dimmed screen returns to normal
I know it's something with the UI Thread and the main Thread and stuff.. But I tried every combination it seems but still not the expected result. Please help :)
EDIT:
HighlightsViewController Class:
- (void)viewDidLoad
{
[super viewDidLoad];
self.updater = [[Updater alloc] init];
[updater checkForUpdatesInContext:self.managedObjectContext];
... // other setup stuff nothing worth mentioning
}
Is this the right place to call [super viewDidLoad]? Because it still doesn't work like this, still the update is being done while the Launch Image is showing on the screen. :-(( I'm about to give this one up..
Here you go, in this prototype things work exactly how you want them to.
Header:
#import <UIKit/UIKit.h>
#interface AlertViewProtoViewController : UIViewController
{
}
- (void) showAlertViewWithTitle:(NSString *)title;
- (void) checkForUpdatesInContext;
- (void) update;
- (void)someMethod;
- (void)someOtherMethod;
#end
#import "AlertViewProtoViewController.h"
Class:
#implementation AlertViewProtoViewController
UIAlertView *alertView;
bool updateDone;
UILabel *test;
bool timershizzle;
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor yellowColor];
UILabel *test = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
test.backgroundColor = [UIColor blueColor];
[self.view addSubview:test];
[self performSelector:#selector(checkForUpdatesInContext) withObject:nil afterDelay:0.0];
}
- (void)update
{
//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //commented for auto ref counting
NSLog(#"update start");
//your update stuff
NSLog(#"update end");
updateDone = YES;
//[pool release];
}
- (void)checkForUpdatesInContext//:(NSManagedObjectContext *)myManagedObjectContext
{
//[self loadUpdateTime];
NSLog(#"Update start");
NSDate *now = [NSDate dateWithTimeIntervalSinceNow:[[NSTimeZone localTimeZone] secondsFromGMT]];
// if ([now timeIntervalSinceDate:updateTime] < UPDATE_TIME_INTERVAL)
// {
// return;
// }
[self showAlertViewWithTitle:#"Update"];
//[self setManagedObjectContext:myManagedObjectContext];
[self performSelector:#selector(someMethod) withObject:nil afterDelay:0.0];
[self performSelector:#selector(someOtherMethod) withObject:nil afterDelay:0.0];
}
-(void)someOtherMethod
{
while (!updateDone) {
// NSLog(#"waiting...");
}
[alertView dismissWithClickedButtonIndex:0 animated:YES];
NSLog (#"Update done");
self.view.backgroundColor = [UIColor greenColor];
}
-(void)someMethod
{
[self performSelectorInBackground:#selector(update) withObject:nil];
}
- (void) showAlertViewWithTitle:(NSString *)title
{
alertView = [[UIAlertView alloc] initWithTitle:title message:#"Daten werden aktualisiert..." delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
alertView.frame = CGRectMake(100, 100, 200, 200);
alertView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:alertView];
[self.view setNeedsDisplay];
NSLog (#"AlertView shows");
}
#end
You should adjust were needed for your own purposes but it works.
You are starting a background thread and then dismissing the alert immediately. I would suggest that you might use an NSNotification, posted from the background task, and received in whichever controller starts the alert, triggering a method that dismissed the alert.
I find the UIAlertView interface unsuitable for this type of user notice, and prefer to use a semi-transparent overlay view with a UIActivityIndicatorView, plus an informing message for the user.
You are doing a:
- (void)applicationDidBecomeActive:(UIApplication *)application
Isn't it so that the alertview you want to show needs a view to be loaded which isn't active yet at this point? See: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAlertView_Class/UIAlertView/UIAlertView.html
Similar question? UIAlertView starts to show, screen dims, but it doesn't pop up until it's too late!