Estimote didChangeAuthorizationStatus not called if isn't in ViewController - ios

I want call startMonitoringForRegion in method didChangeAuthorizationStatus and everything works fine, basically because code is copied from Estimote Example. This code is in ViewController. Issue occurs when I'm using similar code but placed in separated controller.
This code works fine and method didChangeAuthorizationStatus also didStartMonitoringForRegion is called every time so I open app or changeStatus. So success.
ViewController.m
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
self.region = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:#"myUUID"]
identifier:#"EstimoteSampleRegion"];
self.beaconManager = [[ESTBeaconManager alloc] init];
self.beaconManager.delegate = self;
[self startRangingBeacons];
}
-(void)startRangingBeacons
{
if ([ESTBeaconManager authorizationStatus] == kCLAuthorizationStatusNotDetermined)
{
[self.beaconManager requestAlwaysAuthorization];
}
else if([ESTBeaconManager authorizationStatus] == kCLAuthorizationStatusDenied)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Location Access Denied"
message:#"You have denied access to location services. Change this in app settings."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
}
else if([ESTBeaconManager authorizationStatus] == kCLAuthorizationStatusRestricted)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Location Not Available"
message:#"You have no access to location services."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
}
}
- (void)beaconManager:(id)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
if (status != kCLAuthorizationStatusNotDetermined && status != kCLAuthorizationStatusDenied )
{
[self.beaconManager startMonitoringForRegion:self.region];
}
}
- (void)beaconManager:(id)manager didStartMonitoringForRegion:(CLBeaconRegion *)region{
NSLog(#"didStartMonitoringForRegion %#",region);
}
This code works partially. Method requestAlwaysAuthorization in BeaconManager is calling once but then didChangeAuthorizationStatus is not even when I've manually called startMonitoringForRegion then method didStartMonitoringForRegion is not called.
Separeted BeaconManager
ViewController.m
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.region = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:#"myUUID"]
identifier:#"EstimoteSampleRegion"];
self.beaconManager = [[BeaconManager alloc]initWithRegion:self.region];
[self.beaconManager startMonitoring];
}
BeaconManager.swift
class BeaconManager: NSObject,ESTBeaconManagerDelegate {
private lazy var secureBeaconManager:ESTSecureBeaconManager = {
let beaconManagerForReturn = ESTSecureBeaconManager()
beaconManagerForReturn.delegate = self
return beaconManagerForReturn
}()
private lazy var beaconsArray = [CLBeacon]()
var region:CLBeaconRegion!
var delegate:BeaconDelegate?
init(region:CLBeaconRegion) {
super.init()
self.region = region
}
/**
Method which check AuthorizationStatus for application, and will start monitoring if status is equal .AuthorizedAlways
*/
func startMonitoring() {
setupLocalizationAuthorization()
}
private func setupLocalizationAuthorization(){
let status = ESTSecureBeaconManager.authorizationStatus()
if (status == .NotDetermined){
secureBeaconManager.requestAlwaysAuthorization()
}
if (status == .Denied){
delegate?.beaconManager(self, locationAutorizationFail: .Denied)
}
if (status == .Restricted){
delegate?.beaconManager(self, locationAutorizationFail: .Restricted)
}
}
// MARK: - BeaconManager
func beaconManager(manager: AnyObject, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if (status != .NotDetermined && status != .Denied){
secureBeaconManager.startMonitoringForRegion(region)
}
}
func beaconManager(manager: AnyObject, didStartMonitoringForRegion region: CLBeaconRegion) {
print("didStartMonitoring for \(region)")
}
}
Question
Why this is happening? It's thread issue? I have no clue what's going wrong here. From I can see both codes are similar but instructions are placed in different place. It's no matter If I use Swift or Obj-c (tried it already).

Analyze what happens when you start the app for the first time:
You instantiate your BeaconManager and call startMonitoring, which in turn calls setupLocalizationAuthorization. In there, you check the authorization status of your app, and if it's NotDetermined, you instantiate secureBeaconManager (because you declared it a lazy property), assign the delegate (via your lazy-instantion code), and call requestAlwaysAuthorization. This prompts the user to allow the app to access Location Service, and if they agree, iOS issues a call to didChangeAuthorizationStatus with the new, Always status, and you finally you start monitoring. This flow, as you mentioned, works as intended.
Consider however the flow where the app already has the Always authorization:
You instantiate your BeaconManager and call startMonitoring, which in turn calls setupLocalizationAuthorization. In there, you check the authorization status of your app. But you have no if clause for Always status … so the BeaconManager simply finishes there.
Solution: append an if clause for the Always status to your setupLocalizationAuthorization:
if status == .AuthorizedAlways {
secureBeaconManager.startMonitoringForRegion(region)
}
Let's do one final pass over what happens when you implement the proposed solution:
You instantiate your BeaconManager and call startMonitoring, which in turn calls setupLocalizationAuthorization. In there, you check the authorization status of your app. If it's Always, you lazy-instantiate the secureBeaconManager, and start monitoring. The previous flow (for NotDetermined) stays the same.

Related

CLLocationManager startUpdatingLocation not calling locationManager:didUpdateLocations: or locationManager:didFailWithError:

I'm trying to use the CLLocationManager framework in my iOS project to access the user's location but when I call
[locationManager startUpdatingLocation]
neither locationManager:didUpdateLocations: or locationManager:didFailWithError: are getting called.
//myViewController.h
#interface myViewController : UITableViewController <CLLocationManagerDelegate>
#end
//myViewController.m
#implementation myViewController{
CLLocationManager *locationManager;
}
//edit
- (void)viewDidLoad
{
[super viewDidLoad];
locationManager = [[CLLocationManager alloc] init];
}
//finish edit
-(void)getLocation
{
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
UIAlertView *errorAlert = [[UIAlertView alloc]
initWithTitle:#"Error"
message:#"Failed to Get Your Location"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[errorAlert show];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *newLocation = locations[[locations count] -1];
CLLocation *currentLocation = newLocation;
NSString *longitude = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
NSString *latitude = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
if (currentLocation != nil) {
NSLog(#"latitude: %#", latitude);
NSLog(#"longitude: #"%#", longitude);
}else {
UIAlertView *errorAlert = [[UIAlertView alloc]
initWithTitle:#"Error" message:#"Failed to Get Your Location"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[errorAlert show];
}
}
#end
Neither delegate method is being called despite what it says in the documentation:
"This method returns immediately. Calling this method causes the location manager to obtain an initial location fix (which may take several seconds) and notify your delegate by calling its locationManager:didUpdateLocations: method [...] In addition to your delegate object implementing the locationManager:didUpdateLocations: method, it should also implement the locationManager:didFailWithError: method to respond to potential errors."
Don't know how to debug the issue.
Thanks,
JA
Just add this in info.plist
NSLocationAlwaysUsageDescription --- I need Location
NSLocationWhenInUseUsageDescription --- I need Location
privacy - location usage description --- I need Location
Note that "I need Location" should be changed to describe your actual app's designed usage. It is communicated to the end user in the authorization message. (thanks #devios1)
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate=self;
locationManager.desiredAccuracy=kCLLocationAccuracyBest;
locationManager.distanceFilter=kCLDistanceFilterNone;
[locationManager requestWhenInUseAuthorization];
[locationManager startMonitoringSignificantLocationChanges];
[locationManager startUpdatingLocation];
Now it will call your didUpdateToLocation definitely.
for more details click here
You strongly need to check that you initialize CLLocationManager on main thread.
In other case you will not get updateLocation event.
I can't find such info in Apple docs but it works for me anyway.
Location Services work a bit differently starting in iOS 8.
Mainly, you need to add a key NSLocationWhenInUseUsageDescription to your Info.plist file, and add a description why your app need Location, such as "Location needed to ...".
Note that you might also have to check for iOS version. Only iOS 8 and up have the Location Manager listen to the requestWhenInUseAuthorization call.
The link below shows more details:
http://nevan.net/2014/09/core-location-manager-changes-in-ios-8/
Good luck!
With iOS 8.0 you need to call -[CLLocationManager requestWhenInUseAuthorization
] or -[CLLocationManager requestAlwaysAuthorization] first so the user gets asked to give your app permission to use the location.
You need to add below things to your project,
In plist of your project add these things:
Key: NSLocationAlwaysUsageDescription Type:String
Key: NSLocationWhenInUseUsageDescription Type:String
In the .m file with [locationManager startUpdatingLocation] add this condition :
if([locationManager respondsToSelector:#selector(requestAlwaysAuthorization)])
[locationManager requestAlwaysAuthorization];`
Then only your CLLocationManager delegate methods will get called.
I think you should check this link.You are not retaining the location manager object when declaring.
please give property to you object
#property(nonatomic, strong)
Why the CLLocationManager delegate is not getting called in iPhone SDK 4.0?
So if you are running in simulator don't forget to set location in simulator menu. It looks like if it is set to none nothing is called in delegate... I am not sure than if this can happen on real device too but probably it's simulator specific.
I had the following in my code base:
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if( status == kCLAuthorizationStatusAuthorizedAlways ) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
_locationManager.distanceFilter = 200;
}
}
Turns out that this was getting called in an infinite loop, because initializing a locationManager triggers this method for some reason? I had not realized that. I had put it there to catch when permissions were granted. Instead, I set up a location manager and started updating the location, but then this fired and replaced it with one that wasn't updating the location, and kept looping over and over.
Solution for me was just to add && !_locationManager to the if condition.

Why doesn't it find my beacons?

I am writing both Android and iOS apps which need to find BLE beacons around the device.
When I run my code from Android, it finds several beacons in the room I am in.
I have 8 beacons.
When I run the beacon code from iPhone, it returns a list of exactly 0 beacons.
Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self initRegion];
[self locationManager:self.locationManager didStartMonitoringForRegion:self.beaconRegion];
}
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}
- (void)initRegion {
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:BEACONUUID];
self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:BEACONIDENTIFIER];
[self.locationManager startMonitoringForRegion:self.beaconRegion];
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"Beacon Found");
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"Left Region");
[self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
self.beaconFoundLabel.text = #"No";
}
-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
CLBeacon *beacon = [beacons lastObject];//<== is always 0
self.beaconFoundLabel.text = #"Yes";
self.proximityUUIDLabel.text = beacon.proximityUUID.UUIDString;
self.majorLabel.text = [NSString stringWithFormat:#"%#", beacon.major];
self.minorLabel.text = [NSString stringWithFormat:#"%#", beacon.minor];
self.accuracyLabel.text = [NSString stringWithFormat:#"%f", beacon.accuracy];
if (beacon.proximity == CLProximityUnknown) {
self.distanceLabel.text = #"Unknown Proximity";
} else if (beacon.proximity == CLProximityImmediate) {
self.distanceLabel.text = #"Immediate";
} else if (beacon.proximity == CLProximityNear) {
self.distanceLabel.text = #"Near";
} else if (beacon.proximity == CLProximityFar) {
self.distanceLabel.text = #"Far";
}
self.rssiLabel.text = [NSString stringWithFormat:#"%ld", (long)beacon.rssi];
}
In my didRangeBeaconsInRegion, the beacons NSArray always comes up with 0 objects.
Though I have 8 objects. And i've downloaded several apps that are not mine, and they all see several of my beacons.
Why doesn't my code see any of my beacons?
Here is what I do whenever I'm setting up an iBeacon app.
Not all these things are necessary, but it will
work
keep your user happy
(maybe most importantly) keep Apple happy
iOS 8+ Only
First things first: if you're using iOS 8, you need to make sure you actually have access before using CLLocationManager.
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
// You can either use requestAlwaysAuthorization, or requestWhenInUseAuthorization
[self.locationManager requestAlwaysAuthorization];
You'll also need an entry for NSLocationAlwaysUsageDescription in your plist (again, iOS 8 only )
iOS 7+
Your App's pList
Regardless you're using iOS 8 or 7, you should add the following to your plist file (you need to decide if you'll use background or not).
Note: The below is in the form of KEY : KEY TYPE : VALUE for string, and KEY : KEY TYPE : [ Value1, Value2... ] for Arrays:
NSLocationUsageDescription : String : "Gimmie access to your location or else..."
NSBluetoothPeripheralUsageDescription : String : "Gimmie access to your blue tooth or else"
// Optionally supply if you need background modes. I don't believe you need this either if you plan to turn these options on using the Capabilities section of your App's Settings (see below section)
UIBackgroundModes : Array : [ location, bluetooth-central ]
UIApplicationExitsOnSuspend : Boolean : NO
Your App's Project Settings (Capabilities)
this section has been removed as this can cause your app to be rejected (as noted by #heypiotr in the comments)
Final Thoughts
A final suggestion would be to try moving [self locationManager:self.locationManager didStartMonitoringForRegion:self.beaconRegion] into your viewDidAppear.
Here is an example of what I typically do ( which works quite well for me ).
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self initLocationManager];
[self initBeaconRegion];
[self startMonitoring];
}
-(void)initLocationManager {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
// Not necessary, but I like to do it.
if( ![CLLocationManager locationServicesEnabled] ) {
[self.locationManager startUpdatingLocation];
}
// Only necessary if you're in iOS 8. Checking for existence though to support iOS 7
if( ![CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorized ) {
if ([CLLocationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager performSelector:#selector( requestAlwaysAuthorization )];
}
}
}
-(void)initBeaconRegion {
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:kYOUR_UUID_HERE];
self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:kYOUR_IDENTIFIER_HERE];
// Change this to whatever you want....
self.beaconRegion.notifyEntryStateOnDisplay = YES;
self.beaconRegion.notifyOnEntry = NO;
self.beaconRegion.notifyOnExit = YES;
}
# pragma mark -
# pragma mark Monitoring Beacons
# pragma mark -
-(void)startMonitoring {
// Monitor Beacon signals around me and report them back to me
[self.locationManager startMonitoringForRegion:self.beaconRegion];
}
The last part ( placement in viewDidAppear ) may or may not help - it all depends I guess on too many factors to consider in a stackoverflow response.
Hope that helps and good luck!
Final Edit
Forgot one more thing that may help. There are some helpful methods that you can implement that can help you debug the issue further. Here is an example of a few of them:
#pragma mark Authorization Status Changed
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if( ![CLLocationManager locationServicesEnabled] ) {
NSLog(#"Couldn't turn on ranging: Location services are not enabled.");
} else {
NSLog(#"Location services ARE enabled.");
}
if( [CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorized ) {
NSLog(#"Couldn't turn on monitoring: Location services not authorized.");
} else {
NSLog(#"Location services ARE authorized.");
}
}
#pragma mark -
#pragma mark CLLocationManager Errors
#pragma mark -
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog( #"FAIL ERROR: %#", [error description] );
}
-(void)locationManager:(CLLocationManager *)manager rangingBeaconsDidFailForRegion (CLBeaconRegion *)region withError:(NSError *)error {
NSLog( #"RANGE BEACONS ERROR: %#", [error description] );
}
First check with the other app like.Locate app add your UDID and check this app is showing your iBeacon. if not then there is a problem with apple IOS sometimes. then remove the app. Restart the app. it will work for you.
We had similar problem before, make sure your iBeacon device respond the scan request from iOS with a response DOES not contain manufacturer specific field.
Please check this note from apple
Before attempting to monitor any regions, your app should check whether region monitoring is supported on the current device. Here are some reasons why region monitoring might not be available:
The device doesn’t have the necessary hardware to support region
monitoring.
The user denied the app the authorization to use region monitoring.
The user disabled location services in the Settings app.
The user disabled Background App Refresh in the Settings app, either for the device or for your app.
5.The device is in Airplane mode and can’t power up the necessary hardware.
In iOS 7.0 and later, always call the isMonitoringAvailableForClass: and authorizationStatus class methods of CLLocationManager before attempting to monitor regions. (In OS X v10.8 and later and in previous versions of iOS, use the regionMonitoringAvailable class instead.) The isMonitoringAvailableForClass: method tells you whether the underlying hardware supports region monitoring for the specified class at all. If that method returns NO, your app can’t use region monitoring on the device. If it returns YES, call the authorizationStatus method to determine whether the app is currently authorized to use location services. If the authorization status is kCLAuthorizationStatusAuthorized, your app can receive boundary crossing notifications for any regions it registered. If the authorization status is set to any other value, the app doesn’t receive those notifications.
Apple link goes here

Finding out if location services are enabled is not working

I have an app which uses the device location. If they allow the location, I want to run my method getDataFromJson and run my app as normal. If they deny it, or have denied it before, I wish to show them a view explaining they need to go to settings and allow it.
I have a lot of code, but it doesn't work at the moment. Can anyone help explain where the problem is?
Many thanks!
- (void)viewDidLoad
{
[super viewDidLoad];
if ([CLLocationManager authorizationStatus] == YES) {
//are enabled, run the JSON request
[self getDataFromJson];
} else {
//is not enabled, so set it up
NSLog(#"no");
[locationManager location];
};
}
-(CLLocationCoordinate2D) getLocation{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = kCLDistanceFilterNone;
[locationManager startUpdatingLocation];
CLLocation *location = [locationManager location];
CLLocationCoordinate2D coordinate = [location coordinate];
return coordinate;
}
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (status == kCLAuthorizationStatusDenied) {
//location denied, handle accordingly
locationFailView.hidden = NO;
mainView.hidden = YES;
}
else if (status == kCLAuthorizationStatusAuthorized) {
//hooray! begin tracking
[self getDataFromJson];
}
}
//class to convert JSON to NSData
- (IBAction)getDataFromJson {
CLLocationCoordinate2D coordinate = [self getLocation];
NSString *latitude = [NSString stringWithFormat:#"%f", coordinate.latitude];
NSString *longitude = [NSString stringWithFormat:#"%f", coordinate.longitude];
...
}
+ (CLAuthorizationStatus)authorizationStatus
Return Value A value indicating whether the application is authorized
to use location services.
Discussion The authorization status of a given application is managed
by the system and determined by several factors. Applications must be
explicitly authorized to use location services by the user and
location services must themselves currently be enabled for the system.
This authorization takes place automatically when your application
first attempts to use location services.
+ (BOOL)locationServicesEnabled
Returns a Boolean value indicating whether location services are
enabled on the device.
You may check this two state: locationServicesEnabled and authorizationStatus then decide which method should use.
AuthorizationStatus should check with states:
typedef enum {
kCLAuthorizationStatusNotDetermined = 0,
kCLAuthorizationStatusRestricted,
kCLAuthorizationStatusDenied,
kCLAuthorizationStatusAuthorized
} CLAuthorizationStatus;
but you check equal with bool value in viewDidLoad method.

iOS Waiting for Location Manager to Init

I'm developing an iOS app based on the map and location tracking. When the user first boots the app it asks for permission to track location etc. just fine. The only problem is while it's doing that, I have code setting up the initial map view and other location-based variables before the user has clicked OK.
I've found I can put these initiation steps after a while loop that waits for the user to change the location manager's permissions as below but this can't possibly be best practice, not to mention it leads to some strange behavior in the transition between the splash screen and the map:
BOOL firstrun = TRUE;
while ([[locationManager class] authorizationStatus] == kCLAuthorizationStatusDenied || [[locationManager class] authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
NSLog(#"Waiting for location permission");
}
...initiation code...
Is there a "location access granted" listener for the alert box or a similar function in the location manager delegate I don't know about? I see no such method in the docs. Anyone know what the best practice is here? Thank you so much.
EDIT
I start my location tracking as follows:
if (nil == locationManager)
locationManager = [[CLLocationManager alloc] init];
[locationManager startMonitoringSignificantLocationChanges];
self.musicmap.delegate = self;
[self.mymap setShowsUserLocation:true];
Thanks
I would recommend making your class a CLLocationManagerDelegate and then implementing this method:
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
//Your code goes here
}
More information about CLLocationManagerDelegate can be found here.
Hope that helps!
I had a similar problem with my application and the app doing things before the user has time to accept or decline the location permission dialogue. Here is what I ended up doing.
-(BOOL)locationAuthorizationStatus {
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
// user has not authorized us to use location
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Location Denied", #"Location Denied")
message:NSLocalizedString(#"This app does not have permission to access your location. Please enable location access in device settings.", #"Message stating this app does not have permission to access your location and to enable location permission in settings")
delegate:self
cancelButtonTitle:NSLocalizedString(#"Ok", #"Ok")
otherButtonTitles: nil];
[alert show];
return NO;
}
// Check if region monitoring is available for this device
if (![CLLocationManager regionMonitoringAvailable]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Geofencing Unavailable", #"Geofencing Unavailable")
message:NSLocalizedString(#"This device is not able to monitor regions", #"Message stating this device is not able to monitor regions")
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:NSLocalizedString(#"Ok", #"Ok"), nil];
[alert show];
return NO;
} else {
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
// trigger a location check to prompt user for authorization
LocationManagerController *locationController = [LocationManagerController sharedManager];
[locationController.locationManager startUpdatingLocation];
// the dialogue box is triggered here
[locationController.locationManager stopUpdatingLocation];
_waitingOnAuthorization = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(checkForAuthorizationStatusChange) name:#"WaitingOnAuthorizationStatus" object:nil];
return NO;
}
}
return YES;
}
-(void)checkForAuthorizationStatusChange {
if (_waitingOnAuthorization) {
// this should only catch location change on first time
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized) {
// user approved location services
} else {
// user declined authorization
}
// set flag back to NO
_waitingOnAuthorization = NO;
}
// remove our notification observer
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
You will have to add the variables that apply to your use case. But here is the gist of it.
Check for authorization status via BOOL method (locationAuthorizationStatus)
If ok to use location, returns YES do whatever you want
If not, returns NO and alerts user
If first time, will fire up the location manager to trigger location dialogue, then stop it to save battery, sets the flag and sets a notification so you will know when the user has hit yes or no.
The notification fires the method checkAuthorizationChangeStatus and rechecks permissions to see what the user did. From there, you can call any methods you need based on the users choice.
Apple does not have any delegate methods to catch this selection, so the only way around it is kinda of hacky. This method has worked well for me. Slightly hacky, but works. Hope this helps.
//Start up motion manager, not sure if you need this for location manager
motionManager = [[CMMotionManager alloc] init];
if (motionManager.accelerometerAvailable) {
motionManager.accelerometerUpdateInterval = 1.0/2.0;
[motionManager startAccelerometerUpdates];
}
locationManager = [[CLLocationManager alloc] init];
//We will be the location manager delegate
locationManager.delegate = self;
//Track position at the 100m accuracy
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
//We want to see all location updates, regardless of distance change
locationManager.distanceFilter = 0.0;
[locationManager startUpdatingLocation];
Put the above somewhere in your "viewDidLoad" or appDelegate "didFinishLaunchingWithOption"
The following handles the updates and stuff. bestLocation is a CLLocation. You'll need to implement CLLocationManagerDelegate. TWO_MINUTES = 120
- (BOOL)isBetterLocation:(CLLocation *)location {
if (bestLocation == nil){
//best location not set yet, so it's a better location by default
return YES;
}
// Figure out how long it's been since we got a better location
NSTimeInterval timeDelta = [location.timestamp timeIntervalSinceDate:bestLocation.timestamp];
BOOL isSignificantlyNewer = timeDelta > TWO_MINUTES;
BOOL isSignificantlyOlder = timeDelta < -TWO_MINUTES;
BOOL isNewer = timeDelta > 0;
if (isSignificantlyNewer) {
return YES;
}else if (isSignificantlyOlder) {
return NO;
}
CLLocationAccuracy accuracyDelta = location.horizontalAccuracy - bestLocation.horizontalAccuracy;
//You want accuracy to be low
BOOL isLessAccurate = accuracyDelta > 0;
BOOL isMoreAccurate = accuracyDelta < 0;
BOOL isDifferent = location.coordinate.latitude != bestLocation.coordinate.latitude ||
location.coordinate.longitude != bestLocation.coordinate.longitude;
if (isMoreAccurate) {
return YES;
} else if (isNewer && !isLessAccurate && isDifferent) {
return YES;
}
return NO;
}
#pragma mark - Location manager delegate
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
if ([self isBetterLocation:newLocation]){
self.bestLocation = newLocation;
} else {
[locationManager stopUpdatingLocation];
isLocating = NO;
NSLog(#"AppDelegate: Turning off location manager >>>>>>>>>>>>>>>>>>>>>");
}
}
Note: You don't need the "isBetterLocation" method. That will just check the location and turn off the location manager after like 2 minutes so you don't wear down the user's battery. Sometimes you don't want the manager off though, so you can leave it out if you want.
I don't know how much of this you've done already. This is the way I did it.

didEnterRegion works in foreground but not background or other VCs

If the app is running and the CLLocationManagerDelegate class is the foreground (i.e. visible) then the didEnterRegions triggers and I get both the NSLog as well as the AlertView. However, I get nothing when the app is in the background or, essentially, if the screen is showing anything but the delegate class.
I have set "App registers for location updates" under "Required background modes" in the plist although I'm not sure that's even necessary.
Here's what I think is the relevant code although I may be wrong (and will gladly add more). I should note that everything in viewDidLoad is wrapped in an if which checks if region monitoring is available and enabled.
- (void)viewDidLoad
{
NSLog(#"MapViewController - viewDidLoad");
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
self.locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters;
self.locationManager.delegate = self;
[self.locationManager startMonitoringSignificantLocationChanges];
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"MapViewController - didEnterRegion");
NSLog(#"MVC - didEnterRegion - region.radius = %f", region.radius);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"entered region..." message:#"You have Entered the Location." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
alert.tag = 2;
[alert show];
}
here is where I get the list of regions being monitored, in AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// other code
NSLog(#"LISTING ALL REGIONS MONITORED");
NSArray *regions = [self.locationManager.monitoredRegions allObjects];
if (!regions) {
NSLog(#"no regions found");
} else {
NSLog(#"got %d monitored regions", [regions count]);
for (int i = 0; i < [regions count]; i++) {
CLRegion *region = [regions objectAtIndex:i];
NSLog(#"region %d's identifier = %#", i, region.identifier);
NSLog(#"region: radius: %#", region.radius);
}
}
// other code
}
I call startMonitoringForRegion twice, here's the main place:
- (void)doneButtonTapped {
NSLog(#"doneButtonTapped");
if (self.locationIdentifier) {
if ([CLLocationManager regionMonitoringEnabled] && [CLLocationManager regionMonitoringAvailable]) {
// core data setup
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"LocationReminder" inManagedObjectContext:self.managedObjectContext];
fetchRequest.entity = entityDescription;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"locationIdentifier == %#", self.locationIdentifier];
fetchRequest.predicate = predicate;
NSError *error;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (results) {
// get the LocationReminder
LocationReminder *retrievedReminder = [results objectAtIndex:0];
retrievedReminder.audioURI = [[[self.audioPlayers objectAtIndex:self.selectedCell] url] absoluteString];
retrievedReminder.userRecording = nil;
// start monitoring it's region
NSArray *coordinateArray = [retrievedReminder.locationIdentifier componentsSeparatedByString:#", "];
CLLocationCoordinate2D coordinate = {[[coordinateArray objectAtIndex:0] doubleValue], [[coordinateArray objectAtIndex:1] doubleValue]};
CLRegion *newRegion = [[CLRegion alloc] initCircularRegionWithCenter:coordinate radius:250.0 identifier:retrievedReminder.locationIdentifier];
NSLog(#"about to monitor region with radius: %f", newRegion.radius);
[self.locationManager startMonitoringForRegion:newRegion desiredAccuracy:kCLLocationAccuracyBest];
// save the LocationReminder
if (![self.managedObjectContext save:&error]) {
NSLog(#"hmm. no managed object context. must be something space-time going on");
} else {
NSLog(#"saved locationReminder, locationIdentifier = %#", retrievedReminder.locationIdentifier);
}
} else {
NSLog(#"ERROR: no LocationReminder retreived for predicate: %#", predicate);
}
}
// get the mapview controller off of the navigation stack
for (UIViewController *viewController in self.navigationController.viewControllers) {
if ([viewController isKindOfClass:[MapViewController class]]) {
MapViewController *mapVC = (MapViewController *)viewController;
mapVC.audioURI = [[[self.audioPlayers objectAtIndex:self.selectedCell] url] absoluteString];
[self.navigationController popToViewController:mapVC animated:YES];
}
}
}
And because I get the feeling that it might be important, here's the getter for locationManager:
- (CLLocationManager *)locationManager {
NSLog(#"MapViewController - locationManager");
if (_locationManager) {
return _locationManager;
} else {
_locationManager = [[CLLocationManager alloc] init];
return _locationManager;
}
}
UPDATE 1: Via the Apple forums (where I crossposted) someone mentioned that AlertView will only show in the foreground. Still the NSLog doesn't fire either. I'm assuming that should work.
A friend of mine wrote up a nice tutorial on using geofencing that might help clear up some issues you are having.
Get started with geofencing
There are plenty of examples online and here on SO. Start out small and work your way up. Once you start getting your callbacks, you can start expanding things out to your other view controllers.
UPDATE
As explained in the comments the benefits of creating a singleton class to control your location manager and delegate methods. By using a singleton, you prevent the possibility of getting multiple calls to your delegate methods. You can prevent this by careful coding, but using a singleton does this for you. This is also a nice class to handle all the work needing to be done by your delegate methods.
Things you are doing wrong:
Background modes - App registers for location updates. This is not needed. This is need when you want to gather info for significant changes in location etc. So, go to Targets > Your app > Capabilites , and select the desired option under Background modes. This will automatically update the plist for you. For now, disable it.
You are trying to create an alert when the user enters a region. While this while work when app is working, an alert is of no use when your app is in background. Do - Rather trigger a local notification or an api call.
eg. of a notification:
-(void)triggerLocalNotification:(CLRegion *)region{
UILocalNotification *notification = [[UILocalNotification alloc]init];
[notification setAlertBody:[NSString stringWithFormat:#"Welcome to %#", [region identifier]]];
[notification setRepeatInterval:0];
[notification setFireDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[notification setTimeZone:[NSTimeZone defaultTimeZone]];
[[UIApplication sharedApplication]scheduleLocalNotification:notification];
NSLog(#"notification triggered with notification %#", notification);
}
You can post a local notification when you didEnterRegion.
This will show an alert-like popup even if you're in the background.
You can do a simple test:
1) Create a Local notification object inside your applicationDidEnterBackground of your app delegate, with any random message and tell the local notification to fire immediately.
2) Press the home button, when you app minimise, you should see a popup.
i think you need go to your app.plist
and add Required Background modes : add itme App registers for location update
and 1 . if you app is in background , you still see the arrow on top
and 2 , if the app killed , you can still see a hollow arrow on the top , ios will monitor the region for you , but limited to 20 regions

Resources