swifthorseman
swifthorseman

Reputation: 555

Prompt for Location Authorisation Only When MKUserTrackingBarButtonItem Is Tapped

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

Answers (2)

malhal
malhal

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

incanus
incanus

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:

https://github.com/mapbox/mapbox-ios-sdk/blob/509fa7df46ebd654d130ab2f530a8e380bf2bd59/MapView/Map/RMMapView.m#L3299-L3323

https://github.com/mapbox/mapbox-ios-sdk/blob/509fa7df46ebd654d130ab2f530a8e380bf2bd59/MapView/Map/RMMapView.m#L3746-L3753

Upvotes: -1

Related Questions