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.
Related
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.
I am working with Map Kit in iOS 8 using Obj-C NOT SWIFT. I cannot get the device location it is set a 0.00, 0.00 and I am getting the error:
Trying to start MapKit location updates without prompting for location authorization. Must call -[CLLocationManager requestWhenInUseAuthorization] or -[CLLocationManager requestAlwaysAuthorization] first.
I have implemented: ( I have tried only one at a time and no luck )
if(IS_OS_8_OR_LATER) {
[self.locationManager requestWhenInUseAuthorization];
[self.locationManager requestAlwaysAuthorization];
}
[self.locationManager startUpdatingLocation];
And in info.plist
NSLocationWhenInUseUsageDescription : App would like to use your location.
NSLocationAlwaysUsageDescription : App would like to use your location.
I do get prompted to allow the app to use my location but after I agree nothing changes. The location is being showed as 0.00, 0.00.
Code for displaying users location:
//Get Location
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[self.locationManager startUpdatingLocation];
MKCoordinateRegion region = { { 0.0, 0.0 }, { 0.0, 0.0 } };
region.center.latitude = self.locationManager.location.coordinate.latitude;
region.center.longitude = self.locationManager.location.coordinate.longitude;
region.span.longitudeDelta = 0.005f;
region.span.longitudeDelta = 0.005f;
[mapView setRegion:region animated:YES];
Mike.
**EDIT: View Answer Below.
I got it working. I've posted my code below to help anyone else having issues.
Here is my full code to get the MapKit Map View working in iOS 8.
In your AppName-Info.plist
Add a new row with the key name being:
NSLocationWhenInUseUsageDescription
Or
NSLocationAlwaysUsageDescription
With the value being a string of the message that you want to be displayed:
YourAppName would like to use your location.
In your header file. (I use App Name-Prefix.pch but YourViewController.h will work too)
#define IS_OS_8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
YourViewController.h
#import <MapKit/MapKit.h>
#import <MapKit/MKAnnotation.h>
#interface YourViewController : UIViewController <MKMapViewDelegate, CLLocationManagerDelegate> {
}
#property(nonatomic, retain) IBOutlet MKMapView *mapView;
#property(nonatomic, retain) CLLocationManager *locationManager;
YourViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
mapView.delegate = self;
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
#ifdef __IPHONE_8_0
if(IS_OS_8_OR_LATER) {
// Use one or the other, not both. Depending on what you put in info.plist
[self.locationManager requestWhenInUseAuthorization];
[self.locationManager requestAlwaysAuthorization];
}
#endif
[self.locationManager startUpdatingLocation];
mapView.showsUserLocation = YES;
[mapView setMapType:MKMapTypeStandard];
[mapView setZoomEnabled:YES];
[mapView setScrollEnabled:YES];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[self.locationManager startUpdatingLocation];
NSLog(#"%#", [self deviceLocation]);
//View Area
MKCoordinateRegion region = { { 0.0, 0.0 }, { 0.0, 0.0 } };
region.center.latitude = self.locationManager.location.coordinate.latitude;
region.center.longitude = self.locationManager.location.coordinate.longitude;
region.span.longitudeDelta = 0.005f;
region.span.longitudeDelta = 0.005f;
[mapView setRegion:region animated:YES];
}
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, 800, 800);
[self.mapView setRegion:[self.mapView regionThatFits:region] animated:YES];
}
- (NSString *)deviceLocation {
return [NSString stringWithFormat:#"latitude: %f longitude: %f", self.locationManager.location.coordinate.latitude, self.locationManager.location.coordinate.longitude];
}
- (NSString *)deviceLat {
return [NSString stringWithFormat:#"%f", self.locationManager.location.coordinate.latitude];
}
- (NSString *)deviceLon {
return [NSString stringWithFormat:#"%f", self.locationManager.location.coordinate.longitude];
}
- (NSString *)deviceAlt {
return [NSString stringWithFormat:#"%f", self.locationManager.location.altitude];
}
Enjoy!
--Mike
It's not written anywhere, but if your app starts with MapKit, you will still receive the error message "Trying to start MapKit location updates without prompting for location authorization" even after implementing MBarton's answer. To avoid it, you have to create a new view controller before the MapKit, and implement the location manager delegates there. I called it AuthorizationController.
So, in AuthorizationController.h:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface MCIAuthorizationController : UIViewController <CLLocationManagerDelegate>
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
And in AuthorizationController.m:
- (void)viewDidLoad {
[super viewDidLoad];
// Location manager
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
// Check for iOS 8. Without this guard the code will crash with "unknown selector" on iOS 7.
if ([self.locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]) {
[self.locationManager requestWhenInUseAuthorization];
}
}
#pragma mark - Location Manager delegates
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
NSLog(#"didUpdateLocations: %#", [locations lastObject]);
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"Location manager error: %#", error.localizedDescription);
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
[self.locationManager startUpdatingLocation];
[self performSegueWithIdentifier:#"startSegue" sender:self];
} else if (status == kCLAuthorizationStatusDenied) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Location services not authorized"
message:#"This app needs you to authorize locations services to work."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
} else
NSLog(#"Wrong location status");
}
Try This One:
(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
self.mapView.showsUserLocation = YES;
}
Your code looks fine, though you do not need to call requestWhenInUseAuthorization and the other requestAlwaysAuthorization , choose one you need.
Code for displaying locations is just yet allocating locationManager, do not expect to get location data instantly.
you need to wait till delegate method gets called :
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
, then self.locationManager.location will also be set.
Further to Mikes answer, I found that using both [self.locationManager requestWhenInUseAuthorization]; and [self.locationManager requestAlwaysAuthorization]; as demonstrated in his code does not work. You should only use ONE.
I assume some further changes were made with a more recent/stable version of the API.
I had the same problem but adding these two line in plist file solved my problems
NSLocationWhenInUseUsageDescription
And
NSLocationAlwaysUsageDescription
NOTE : Must provide string description of both these values. You can use any of them in your controller file as below
self.locationManager= [[CLLocationManager alloc] init];
self.locationManager.delegate=self;
[self.locationManager requestAlwaysAuthorization];
You must implement CLLOcationManagerDelegate in your controller to access this functionality
To extend the accepted answer and if you create a sample project with just the above functionality, then apart from CoreLocation and Mapkit frameworks, you might need to add UIKit, Foundation and CoreGraphics framework manually as well in Xcode 6.
Actually, I am studying the CS193P Lecture 16, which is about location and map view, and I could not make the location manager work in iOS 8, applying what was in the video.
Looking at your answer, I could make it work.
The Info.plist was modified as described in the answers (I use the NSLocationWhenInUseUsageDescription).
In AddPhotoViewController.hn the define was added :
#define IS_OS_8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
In AddPhotoViewController.m, the following code was added in ViewDidLoad (after self.image):
#ifdef __IPHONE_8_0
if(IS_OS_8_OR_LATER)
{
[self.locationManager requestWhenInUseAuthorization];
}
#endif
The authorization will be asked only once, the first time you launch the application.
The following was also added to AddPhotoViewController.h because it was not said in Lecture 16 :
#property (nonatomic) NSInteger locationErrorCode;
shouldPerformSegueWithIdentifier was modified to include else if (!self.location) :
else if (![self.titleTextField.text length])
{
[self alert:#"Title required"];
return NO;
}
else if (!self.location)
{
switch (self.locationErrorCode)
{
case kCLErrorLocationUnknown:
[self alert:#"Couldn't figure out where this photo was taken (yet)."]; break;
case kCLErrorDenied:
[self alert:#"Location Services disabled under Privacy in Settings application."]; break;
case kCLErrorNetwork:
[self alert:#"Can't figure out where this photo is being taken. Verify your connection to the network."]; break;
default:
[self alert:#"Cant figure out where this photo is being taken, sorry."]; break;
}
return NO;
}
else
{ // should check imageURL too to be sure we could write the file
return YES;
}
didFailWithError was added :
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
self.locationErrorCode = error.code;
}
Is there any method that take latitude and longitude as parameter and google map link or apple map link these three parameter to show user location on google map or apple map so user can use google created google map functionality like arrow direction or zoom in/out.
I don't want to use MKMapView of iOS to show user location in my app.
thanks in advance give me any cool method(latitude,longitude,google map link)as parameter.
For coordinates only, you need CoreLocation, so google that.
But if you need to show a map and arrow, you do need a maps framework. Either go for built in MKMapKit, or you can download Google Maps SDK.
First of all , Please import this framework
#import
, to import this framework , you need to select your project then go to build phases , click on link binaries with libraries and add the core location framework .
Add this code in your view controller in view didload method:-
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest; // 100 m
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
[locationManager requestAlwaysAuthorization];
}
[locationManager startUpdatingLocation];
// Delegates of Mapview
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, 800, 800);
[map_View setRegion:[map_View regionThatFits:region] animated:YES];
if (newLocation != nil) {
NSLog(#"%f %f",currentLocation.coordinate.longitude,currentLocation.coordinate.latitude);
[locationManager stopUpdatingLocation];
locationManager = nil;
}
// Reverse Geocoding
CLGeocoder *geocoder = [CLGeocoder new];
dispatch_async(dispatch_get_main_queue(), ^{
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
if (error == nil && [placemarks count] > 0) {
placemark = [placemarks lastObject];
} else {
NSLog(#"%#", error.debugDescription);
}
} ];
});
}
Don't forget to add this line in your plist file :-
NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription
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.
I've got a UIPickerView object that lists stores based on either a listed "home" location or one's current location (using CLLocationManager). If the latter is implemented, I make a NSMutableURLRequest to my server to get the closest stores, then update a UIPickerView with the received list.
On occasion, (and oddly enough never when I'm at the "home" location), I will use the current location, I'll see the picker update the list, then the app immediately crashes.
My picker code is simple enough:
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}
-(NSInteger) pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (isHome) {
return [storesData count];
} else {
return [storesDataLoc count];
}
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
if (isHome) {
return [[storesData objectAtIndex:row] objectForKey:#"STName"];
} else {
return [[storesDataLoc objectAtIndex:row] objectForKey:#"STName"];
}
}
One thought was that it was providing a second, more accurate reading and that I was releasing something that I may have already released. My CLLocationManager code is:
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
if (error.code == kCLErrorLocationUnknown) {
NSLog(#"Currently unable to retrieve location");
} else if (error.code == kCLErrorNetwork) {
NSLog(#"Network used to retrieve location is unavailable");
} else if (error.code == kCLErrorDenied) {
NSLog(#"Permission to retrieve location is denied");
[locMan stopUpdatingLocation];
[locMan release];
locMan = nil;
// revert segmented controller to Home position
storeSource.selectedSegmentIndex = 0;
}
if(loadstoresconnection!=nil){
[loadstoresconnection release];
}
networkView.hidden = TRUE;
isHome = TRUE;
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
if (newLocation.horizontalAccuracy >= 0) {
networkView.hidden = TRUE;
if (newLocation.horizontalAccuracy < 200) {
[locMan stopUpdatingLocation];
[locMan release];
locMan = nil;
}
//call for store list from this location
NSString *myString = [NSString stringWithFormat:#"http://mywebsite.com/?lat=%f&lng=%f",newLocation.coordinate.latitude,newLocation.coordinate.longitude];
NSURL *myURL = [[NSURL alloc] initWithString:[myString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
loadstoresconnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
}
And the pertinent NSMutableURLRequest methods are:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[responseData release];
[connection release];
networkView.hidden = TRUE;
isHome = YES;
// [textView setString:#"Unable to fetch data"];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
int i;
NSArray *querydata;
networkView.hidden = TRUE;
[loadstoresconnection release];
if (storesDataLoc!=nil) {
[storesDataLoc release];
storesDataLoc = nil;
}
storesDataLoc = [[NSMutableArray alloc] init];
NSString *txt = [[[NSString alloc] initWithData:responseData encoding: NSASCIIStringEncoding] autorelease];
[responseData release];
// put data into variables.
querydata = [txt componentsSeparatedByString:#"<-sl->"];//break up data into data sections: 0 - number of deptsections and names, 1 - list of objects
NSArray *allstoreinfo;
for (i=0; i<[querydata count]; i++) {
allstoreinfo = [[querydata objectAtIndex:i] componentsSeparatedByString:#"<-as->"];
[storesDataLoc addObject:[[[NSMutableDictionary alloc] initWithObjectsAndKeys:[allstoreinfo objectAtIndex:0],#"STid",[allstoreinfo objectAtIndex:1],#"STName",[allstoreinfo objectAtIndex:2],#"STAddr",nil] autorelease]];
}
if ([querydata count]>0) {
[pickerView reload];
[pickerView selectRow:0 inComponent:0 animated:NO];
isHome = NO;
}
}
Im curious as to why I can see the picker being updated just before the crashes. Because this happens when I'm on the road, I'm suspecting it's an accuracy thing, and location manager is sending a second result, causing the crash. Any thoughts?
It appears I was trying to do too much of a shortcut. When the user hit the location button, it would turn on the LocationManager, which with every result would set up an NSURLConnection to my server. I was bring to handle all this in a single class, but with the multiple results the location manager would return, NSURLConnections seemed to be uncontrolled. I have since put each location manager result in its own class and everything is working honky dory. So, no issue with the picker - mainly a memory issue with location manager and nsurlconnections.