Friday, November 11, 2011

Location Services : Simulating Locations

This series discusses the iPhone Location Services.  In this post, we learn how to change the default iOS Simulator location and simulate other locations.

Default Location
In the last post, we got the LSDemo app up and running and saw a stream of updates like this in the Xcode console:
2011-11-10 11:44:15.453 LSDemo[20964:f803] newLocation=<+37.78583400,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/10/11 11:44:15 AM Pacific Daylight Time
2011-11-10 11:44:15.456 LSDemo[20964:f803] oldLocation=<+37.78583400,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/10/11 11:44:14 AM Pacific Daylight Time
Notice the location each time is <+37.78583400,-122.40641700>.  A simple Google Maps query reveals this corresponds to the Apple store in San Francisco:
Why is the iOS Simulator using this location, and how can we change it?  

To understand where this is coming from, launch LSDemo in the iOS Simulator.  Open the Debug > Location menu and notice that "Custom Location" is checked:
Click on it to reveal the custom location:
Yay!  Now we know why LSDemo keeps reporting a location of <+37.78583400,-122.40641700>.  This is the default custom location that comes with Xcode.


Simulating Locations
What if we would like to simulate another location?  At first glance, the choices provided by the iOS Simulator  Debug > Location menu appear limited:
To simulate a more exotic location, we could manually look up the GPS coordinates, and then paste them each time into the "Custom Location" dialog.  However, this can be tedious.  Fortunately Xcode offers another way to simulate locations.  While the LSDemo is running in iOS Simulator, click the GPS arrow icon (circled in red):
This will reveal a list of pre-programmed alternatives:
For fun, let's select Sydney, Australia.  As the LSDemo app is running in the iOS Simulator, the new location should be reflected in the console log:
2011-11-10 11:52:08.365 LSDemo[21129:f803] oldLocation=<+37.78583400,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/10/11 11:52:07 AM Pacific Daylight Time
2011-11-10 11:52:09.056 LSDemo[21129:f803] newLocation=<-33.86340000,+151.21100000> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/10/11 11:52:09 AM Pacific Daylight Time
Notice how the location changed, from <+37.78583400,-122.40641700>  to  <-33.86340000,+151.21100000>.  (Color added for emphasis.)  A simple Google Maps query reveals this is indeed Sydney, Australia:
Yay!  We are receiving the new location updates as we expected.  

Depending on the application, a constant stream of updates may be useful.  However, if we just want a location fix, as we will for HelloWorld, the continuous updates will be overkill.  How do we stop the updates once we have our location?  We shall learn that in the next post.

Location Services : iOS Location Services Settings

This series discusses the iPhone Location Services.  In this post, we learn about the iOS Location Services Settings.

iOS Location Services Settings
In Standard Location Provider III, we hooked up the StandardLocationProvider, but we denied LSDemo use of the iPhone Location Services.  As a result, whenever we launch the app, we get an error:
2011-11-10 11:27:50.285 LSDemo[20652:f803] error=Error Domain=kCLErrorDomain Code=1 "The operation couldn’t be completed. (kCLErrorDomain error 1.)"
To fix this, click the Home button on the simulated iPhone and open the Settings app.
Click the "Location Services" setting.  

Notice the location services for the sandbox app "LSDemo" have been disabled.  Turn it On by sliding the switch:

Finally, press the Home button again and re-launch LSDemo app.  In the Xcode console, you should now see a stream of updates like this:
2011-11-10 11:33:29.432 LSDemo[20715:f803] newLocation=<+37.78583400,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/10/11 11:33:29 AM Pacific Daylight Time
2011-11-10 11:33:29.433 LSDemo[20715:f803] oldLocation=(null)
2011-11-10 11:33:30.278 LSDemo[20715:f803] newLocation=<+37.78583400,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/10/11 11:33:30 AM Pacific Daylight Time
2011-11-10 11:33:30.280 LSDemo[20715:f803] oldLocation=<+37.78583400,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/10/11 11:33:29 AM Pacific Daylight Time
(Your locations may vary.)  Yay!  The app is behaving just as we expected.


Disabling Location Services
What happens if we disable Location Services completely?  Let's try.  Click the Home button again and open the Settings app.  This time though, disable all Location Services by sliding the switch at the top to Off:
Then re-launch LSDemo.  You will see this alert popup:
If you click "Cancel" at this point, the app will just hang.  There will be no error and no location updates will be sent.  If we re-launch the app and click "Cancel" a second time, LSDemo will be completely disabled, and the alert will stop appearing altogether.  To restore the alert popup, delete LSDemo from the iPhone simulator entirely and re-launch the app from within Xcode.

This time, click "Settings" to be taken back to the Location Services screen.  Slide the switch to turn them back On, and likewise enable Location Services for LSDemo.  Click the Home button in the simulator and return to the app.  Now, you should again see the familiar stream of updates in the Xcode console:
2011-11-10 11:44:15.453 LSDemo[20964:f803] newLocation=<+37.78583400,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/10/11 11:44:15 AM Pacific Daylight Time
2011-11-10 11:44:15.456 LSDemo[20964:f803] oldLocation=<+37.78583400,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/10/11 11:44:14 AM Pacific Daylight Time
Yay!  Now we know what happens when Location Services are disabled.

Notice the coordinates <+37.78583400,-122.40641700>.  Where is the app getting this location, and how can we change it?  In the next post we shall find out.

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?