Reputation: 555
I am using MKUserTrackingBarButtonItem
(i.e., the compass icon) to display and track the user location in MKMapView
when it is tapped. This works fine in iOS 7. In iOS 8, I started getting the following message:
Trying to start MapKit location updates without prompting for location authorization. Must call -[CLLocationManager requestWhenInUseAuthorization] or -[CLLocationManager requestAlwaysAuthorization] first.
I understand that the below code must be added:
if ([self->_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)])
{
[self->_locationManager requestWhenInUseAuthorization];
}
and the Info.plist file must contain:
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
My question is where to place the call to requestWhenInUseAuthorization
. If I place it in didChangeUserTrackingMode
, it is too late and I get the aforementioned error message. If I place it in viewDidLoad
when initialising CLLocationManager
, it works fine but it changes the behaviour of the screen, i.e., when loading the map, the user's location is not tracked, therefore the user should get the prompt only when the MKUserTrackingBarButtonItem
button is tapped.
Upvotes: 2
Views: 446
Reputation: 30575
First set up a static context for the observer (Place this above your @implementation), we will use the address (&) of it to give us a unique context for this class:
static NSString* kShowsUserLocationChanged = @"kShowsUserLocationChanged";
Next, you need to observe the showsUserLocation property of your MKMapView:
[self.mapView addObserver:self
forKeyPath:@"showsUserLocation"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:&kShowsUserLocationChanged];
Then you need a method to request authorisation if it previously hasn't been determined, and as a bonus, my method also checks if the Info.plist has been configured correctly, and alerts the developer if it's not the case:
-(void)_requestLocationAuthorizationIfNotDetermined{
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined){
BOOL always = NO;
if([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"]){
always = YES;
}
else if(![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]){
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Location usage description missing from Info.plist" userInfo:nil];
}
static CLLocationManager* lm = nil;
static dispatch_once_t once;
dispatch_once(&once, ^ {
// Code to run once
lm = [[CLLocationManager alloc] init];
});
if(always){
[lm requestAlwaysAuthorization];
}else{
[lm requestWhenInUseAuthorization];
}
}
}
Finally, you need to add the observe method implementation:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
// if it was our observation
if(context == &kShowsUserLocationChanged){
// if its being enabled
if([[change objectForKey:NSKeyValueChangeNewKey] boolValue]){
[self _requestLocationAuthorizationIfNotDetermined];
}
}
else{
// if necessary, pass the method up the subclass hierarchy.
if([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]){
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
}
You can test it by uninstalling the app which clears the authorisation status for the next deploy and run.
Upvotes: 1
Reputation: 5128
I've had success putting the call to requestWhenInUseAuthorization
when it's first needed, but then responding to didChangeAuthorizationStatus
to start the tracking.
Example:
Upvotes: -1