8.1. The Core Location Manager

All Core Location functions are performed through a Core Location manager. The manager's function is to act as a query interface for your application. You'll instantiate a manager to set up and execute location-based queries, and again when you're finished, to turn the GPS off. The manager is instantiated as a CLLocationManager object. Queries for your location are not returned immediately. Instead, your application will be notified when information (or updates) to the user's whereabouts are ascertained. This information is periodically sent to the manager's delegate, which must implement the CLLocationManagerDelegate protocol. Subsequent updates to the device's location are sent as Core Location begins receiving more accurate information, or if the device is moved beyond a distance threshold set by the programmer.

In Example 8-1, you'll create your own class to act as a query class. The query class will act as delegate, invoke the Core Location manager class, receive notifications, and eventually release the manager when finished.

Example 8-1. MyCLQuery class definition
@interface MyCLQuery : NSObject
{
    CLLocationManager *manager;
}
- (void)startQuery;
- (void)stopQuery;
- (void)locationManager:(CLLocationManager *)locationManager
    didUpdateToLocation:(CLLocation *)newLocation
    fromLocation:(CLLocation *)oldLocation;

 

The MyCLQuery example class uses three methods. The startQuery method sets up the CLLocationManager object and begins the location query process. Once activated, the location manager runs in the background to determine your current position. As a fix is acquired, or the location changes, the didUpdateToLocation method is notified. This is the method responsible for reporting the new location to your application. Finally, when you're finished using Core Location, the stopQuery method must be called to turn off updates.

8.1.1. Query Parameters

You will need to supply some query parameters to tell Core Location what kind of information you need. These pertain to the level of accuracy needed, and when to send updates to your delegate. For example, an application designed to find the nearest restaurant might require less specific information than a geo-caching application, which would need very specific data about the user's position. If your application will be used at high velocities (such as while driving or on a bus), it might only require updates every few hundred feet; an application designed to be used on foot would call for more frequent updates.

You should configure at least three parameters before instructing the manager to begin a location lookup:



delegate

This is the object that will receive location updates. The delegate object will need to have a proper implementation of the CLLocationManagerDelegate protocol. The delegate will be notified through its didUpdateToLocation method to receive these notifications from the Core Location manager, as shown in Example 8-1. In the examples throughout this chapter, MyCLQuery object will serve as the delegate.



desiredAccuracy

Instructs the location manager as to what level of accuracy you need in your application. To conserve battery and to avoid unnecessary updates, use the least specific level of accuracy required for your particular purpose. Different levels of accuracy are returned by different facilities; for example, if only a single cell tower is within the signal's reach, Core Location might initially return a very large location breadth. As Core Location continues to receive more specific information back from WiFi lookups and the GPS, it will notify your application of the better fix. You may use the following settings, which instruct Core Location just how hard to work to acquire the user's position:



kCLLocationAccuracyBest

Deliver the best possible accuracy



kCLLocationAccuracyNearestTenMeters

Deliver accuracy within 10 meters (about 30 feet)



kCLLocationAccuracyHundredMeters

Deliver accuracy within 100 meters (about 300 feet)



kCLLocationAccuracyKilometer

Deliver accuracy within 1 kilometer (about 6/10 of a mile)



kCLLocationAccuracyThreeKilometers

Deliver accuracy within 3 kilometers (about a mile and a half)

NOTE

 

One meter is equal to 1.09 yards. One kilometer is equal to 0.62 miles.



distanceFilter

Sets the minimum amount of movement (in meters) before an update is sent to the delegate. Further updates will not be sent unless the device's position has moved by at least this value, or if the level of accuracy improves. To send updates for any movement, use the value kCLDistanceFilterNone.

8.1.2. Issuing a Query

Once you've determined what kind of Core Location query your application requires, you can instantiate a manager and assign it your query parameters. In the example below, a startQuery method is used to set up the query and invoke Core Location. The code below requests the best possible accuracy and asks for continual updates for any movement. Finally, it invokes the Core Location manager's startUpdatingLocation method, which initiates a location lookup and notifications to follow:

- (void) startQuery
{
    manager = [[ CLLocationManager alloc ] init ];

    manager.delegate = self;

    manager.desiredAccuracy = kCLLocationAccuracyBest;
    manager.distanceFilter = kCLDistanceFilterNone;

    NSLog(@"Core Location updates started");
    [ manager startUpdatingLocation ];
}

8.1.3. Receiving Updates

The MyCLQuery object in our example will serve as the delegate, so you'll need to implement the delegate's protocol methods. The Core Location manager provides two results pointing to both the new and old locations, making it easy to recognize movement. The following information is available from both of these CLLocation objects:



Longitude and latitude

The longitude and latitude is read through the coordinate structure, accessible from the CLLocation object. This structure contains latitude and longitude members, both represented as double floating point; this is typed to a CLLocationDe⁠grees data type.



Altitude

The altitude property reads the altitude in meters. This value can be positive or negative, indicating the number of meters above or below sea level. This value is returned as a double floating point, and is typed to a CLLocationDistance.



Accuracy

The horizontal and vertical accuracy is reported as a CLLocationAccuracy (double floating point) type. These values, accessible via the horizontalAccuracy and verticalAccuracy properties, will reflect the accuracy (in meters) of the coordinates and altitude provided. You're not always guaranteed to get the level of accuracy you requested, so it's important to check this to determine the overall radius of the position returned. As the accuracy continues to improve, these values will also be updated in subsequent notifications.



Timestamp

Updates are sent periodically as the accuracy of the GPS improves, or when the device is moved a given distance. If your application is time-critical, you can ensure that the data being reported back to it is recent by checking the timestamp property, which is an NSDate object containing the timestamp of the query.

Because Core Location uses many different facilities to acquire a location fix, it's possible that newer fixes might report back to the application with older timestamps than the previous location. For example, a global positioning query may take several seconds to return, whereas a WiFi location lookup may take less than a second. As a result, the more specific location returned with the satellite query will have an older query timestamp. The best way to handle this is to accept the newer information if it is more specific, and provide a visual indicator to the user that your application has narrowed down his location.



Description

Core Location also provides an NSString object describing the overall location. You can access this through the description property. Apple does not guarantee the formatting, so because this is subject to change, it is not a very good idea to parse data based on the description. It can be useful, however, to display to the user, or for debugging.

The example below receives notifications sent by the Core Location manager and logs the output to the console:

- (void)locationManager:(CLLocationManager *)locationManager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
    NSLog(@"Old Coordinates: %+.6f, %+.6f\n",
        oldLocation.coordinate.latitude,
        oldLocation.coordinate.longitude);
    NSLog(@"New Coordinates: %+.6f, %+.6f\n",
        newLocation.coordinate.latitude,
        newLocation.coordinate.longitude);
    NSLog(@"Altitude: %.6f\n", newLocation.altitude);
    NSLog(@"Description: %@\n", [ newLocation description ]);

    NSDate *timestamp = newLocation.timestamp;
    NSTimeInterval age = [ timestamp timeIntervalSinceNow ];
    NSLog(@"Notification age: %.4f\n", age);
}

8.1.4. Completing a Query

When your application is satisfied with the location information it has received, be sure to stop the manager to avoid draining the battery. This will also stop updates from being sent to your application. You can accomplish this through a simple call to the manager's stopUpdatingLocation method:

- (void) stopQuery
{
    [ manager stopUpdatingLocation ];
    [ manager release ];
}

8.1.5. Error Handling

An error can occur if the current position couldn't be ascertained or if the user denied the application from using his current location. When either of these occurs, a method named didFailWithError will notify the delegate of a failure:

- (void)locationManager:(CLLocationManager *)locationManager
        didFailWithError:(NSError *)error
{
    NSLog(@"Core Location failed with error: %@\n", [ error code ]);
}

The didFailWithError method supplies an NSError object, which is a standard Cocoa object used to report errors. The error code returned with the NSError object identifies the type of failure:



kCLErrorLocationUnknown

Could not ascertain location. When this error is indicated, Core Location continues trying to get a fix in the background and will send an update if one becomes available. If your application decides to time out the query, ensure that it uses the manager's stopUpdatingLocation method to shut down Core Location.

When this occurs, its best to notify the user that your application had trouble determining his location while still giving him the impression that it is continuing to scan. The easiest way to do this is with an activity indicator, which is explained in Chapter 10.



kCLErrorDenied

The user denied your application access to Core Location. When this occurs, your application should be prepared to either continue on without the location or prompt the user for manual input, such as a zip code.

8.1.6. WhereYouAt: Redneck Core Location

If you've ever lived in the south, at some point someone has used the standard redneck vernacular, "where you at?" to acquire your position. This example demonstrates Core Location by querying a CLLocationManager object for the current location. When provided, this information will be presented in a text view to the user. Like most rednecks, it's nothing fancy—just a lot of fun.

This application, shown in Examples Example 8-2 through Example 8-6 can be compiled with the SDK by creating a view-based application project named WhereYouAt. Be sure to pull out the Interface Builder code if you'd like to see how these objects are created from scratch.

Example 8-2. WhereYouAt application delegate prototypes (WhereYouAtAppDelegate.h)
#import <UIKit/UIKit.h>

@class WhereYouAtViewController;

@interface WhereYouAtAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    WhereYouAtViewController *viewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet WhereYouAtViewController *viewController;

@end

                                          

Example 8-3. WhereYouAt application delegate (WhereYouAtAppDelegate.m)
#import "WhereYouAtAppDelegate.h"
#import "WhereYouAtViewController.h"

@implementation WhereYouAtAppDelegate

@synthesize window;
@synthesize viewController;


- (void)applicationDidFinishLaunching:(UIApplication *)application {
    CGRect screenBounds = [ [ UIScreen mainScreen ] bounds ];

    self.window = [ [ [ UIWindow alloc ] initWithFrame: screenBounds ]
        autorelease
    ];

    viewController = [ [ WhereYouAtViewController alloc ] init ];

    [ window addSubview: viewController.view ];
    [ window makeKeyAndVisible ];
}

- (void)dealloc {
    [ viewController release ];
    [ window release ];
    [ super dealloc ];
}


@end

Example 8-4. WhereYouAt view controller prototypes (WhereYouAtViewController.h)
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>

@interface WhereYouAtViewController : UIViewController <CLLocationManagerDelegate> {
    UITextView *textView;
    CLLocationManager *gps;
}

@end

                                          

Example 8-5. WhereYouAt view controller (WhereYouAtViewController.m)
#import "WhereYouAtViewController.h"

@implementation WhereYouAtViewController

- (void)loadView {
    [ super loadView ];
    textView = [ [ UITextView alloc ] initWithFrame: self.view.frame ];
    self.view = textView;
}

- (void)viewDidLoad {
    textView.text = @"Where you at? ...";
    gps = [ [ CLLocationManager alloc ] init ];
    gps.delegate = self;
    gps.desiredAccuracy = kCLLocationAccuracyBest;
    gps.distanceFilter = kCLDistanceFilterNone;
    [ gps startUpdatingLocation ];

}

- (void)locationManager:(CLLocationManager *)locationManager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation;
{
    textView.text = [ NSString stringWithFormat: @"You now at...\n\n"
        "Description: %@\n"
        "Coordinates: %f, %f\n"
        "Altitude: %f\n"
        "Updated: %@\n",
        newLocation.description,
        newLocation.coordinate.latitude,
        newLocation.coordinate.longitude,
        newLocation.altitude,
        newLocation.timestamp ];
}

- (void)didReceiveMemoryWarning {
    [ super didReceiveMemoryWarning ];
}

- (void)dealloc {
    [ textView release ];
    [ super dealloc ];
}
@end

                                          

Example 8-6. WhereYouAt main (main.m)
#import <UIKit/UIKit.h>

int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
    int retVal = UIApplicationMain(argc, argv, nil, @"WhereYouAtAppDelegate");
    [ pool release ];
    return retVal;
}

                                          

8.1.7. What's Going On

The WhereYouAt demo is a simple, yet useful example. Here's how it works:

  1. When the application instantiates, it notifies the WhereYouAtAppDelegate class, which in turn builds the window and view controller.

  2. When the view controller is loaded, it builds a UITextView object and sets it as the active view. This is where information is displayed to the user.

  3. After the view is loaded, the viewDidLoad view controller method creates an instance of the CLLocationManager class and issues a query.

  4. Whenever new location information is ascertained, the didUpdateToLocation delegate method is invoked with the latest location information. This is, in turn, displayed to the user.

8.1.8. Further Study

  • You can find a great wealth of information about Core Location queries in CLLocation.h and CLLocationManager.h. You'll find these deep within your SDK's CoreLocation.framework headers in /Developer/Platforms/iPhoneOS.platform.