Tuesday, November 1, 2011

Location Services : Standard Location Provider III

This series discusses the iPhone Location Services.  In this post, we hook the StandardLocationProvider into the LSDemo app.

LSDemo App
In Standard Location Provider II, we finished implementing the StandardLocationProvider.  Now let's hook it back into the LSDemo app so we can exercise our prototype code.

We want the prototype to execute as though it were running inside a real iPhone app.  Recall that after an app's UIViewController loads its associated views into memory, the viewDidLoad method is invoked to allow for additional initialization steps.  This is where we will instantiate our prototype.

Open the LSDemo view controller header file (ViewController.h) and declare the locationProvider field: 
#import "StandardLocationProvider.h"

@interface ViewController : UIViewController
{
  @private
    StandardLocationProvider *locationProvider;
}

@end
(Again, it is marked @private because we do not need to expose the internals of the class.)  Now open up the corresponding implementation file (ViewController.m) and instantiate the locationProvider:
- (void)viewDidLoad
{
    [super viewDidLoad];
   
    // Do any additional setup after loading the view.
    locationProvider = [[StandardLocationProvider allocinit];
}
Finally, remember to relinquish the locationProvider reference in the viewDidUnload method:
- (void)viewDidUnload
{
    [super viewDidUnload];
   
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    locationProvider = nil;
}
At this point, everything is hooked up.  Build (but don't yet launch) the sandbox app to make sure there are no errors.

iOS Location Services Alert Popup
The first time an iPhone app requests location services from iOS, an alert popup should appear asking the user to allow the service.  As the CLLocationManager documentation explains:
During its initial uses by an application, the Core Location framework prompts the user to confirm that using the location service is acceptable.
Let's verify.  Go ahead and start the LSDemo app.  The following alert popup should appear:

Yay!  Our prototype is behaving as expected.  However, rather than clicking "OK" right away, let's take a small detour.  We shall return here briefly.

Core Location Errors
What happens if we click "Don't Allow" instead?  When the user refuses an app access to location services, we expect the app to be notified via its CLLocationManagerDelegate, specifically the locationManager:didFailWithError: method.  As the documentation explains:
If the user denies your application’s use of the location service, this method reports a kCLErrorDenied error.
Let's verify.  Click the "Don't Allow" button to refuse our prototype access to location services.  The popup should go away, and something like the following should be logged to the console:
2011-11-01 10:58:52.172 LSDemo[276:f803] error=Error Domain=kCLErrorDomain Code=1 "The operation couldn’t be completed. (kCLErrorDomain error 1.)"
An error was generated, as we expected.  But is it the right error?

Let's take a closer look.  From the documentation, we know the error sent back is an instance of NSError.  The key properties of this class are its domain and a domain-specific error code.  Looking at the error message above, we can deduce the following:
  • domain = kCLErrorDomain
  • code = 1
As the Core Location Constants Reference explains, kCLErrorDomain indeed corresponds to the domain for Core Location framework errors.  The error codes for this domain are listed in the CLError enumerated type definition.  Digging deeper, we find an error code of 1 corresponds to the kCLErrorDenied element.  As the documentation confirms, this code means access to the location service was denied by the user.

Yay!  The error generated by clicking "Don't Allow" is consistent with expectations.

Now quit the simulator and try again.  All subsequent attempts to run the sandbox app will result in this error.  What's more, the popup has gone too.  How do we fix this?