Bookmark and Share

iPhone SDK Tutorial
Chapter 13. Core Location





13.0 Preview

There are three technologies that Core Location can use to locate a position.

  • GPS
    The most accurate of the three, and it reads microwave signals from multiple satellites to determine the current location.
  • Cell Tower Triangulation
    It determines the current location by doing a calculation based on the locations of the cell towers in the phone's rang. Cell tower triangulation can be fairly accurate in cities with a high cell tower density but becomes less accurate in areas where there is a greater distance between towers. is a g
  • Wi-Fi Positioning Service (WPS)
    WPS uses the IP address from iPhone's Wi-Fi connection to make a a guess at your location by referencing a large database of known service providers and the areas they service. It is imprecise and can be off by many miles.

All the three methods can be a drain on iPhone's battery. When using Core Location, we have the option of specifying an accuracy, and we should be very careful when we specify the minimum accuracy level we need, otherwise it causes unnecessary battery drain.






13.1 Location Manager

The main class we'll work with is CLLocationManager,usually referred to as Location Manager. In order to interact with Core Location, we need to create an instance of the Location Manager:

CLLocationManager *locationManager = [[CLLocationManager alloc] init];

This creates an instance of the Location Manager for us, but it doesn't actually start polling for our location. We have to assign a delegate to the Location Manager. The Location Manager will call delegate methods when location information becomes available or changes. The process of determining location may take some time. The delegate must conform to the CLLocationManagerDelegate protocol.

After we set the delegate, we also want to set the requested accuracy. An example of setting the delegate and requesting a specific level of accuracy:

locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;

The accuracy is set using CLLocationAccuracy value, a type that's defined as a double. The value is in meters, so if we specify a desiredAccuracy of 10, we're telling Core Location that we want it to try to determine the current location within 10 meters.
Specifying kCLLocationAccuracyBest tells Core Location to use the most method that's currently available.

By default, the Location Manager will notify the delegate of any detected change in location. By specifying a distance filter, we are telling Location Manager not to notify us for every change and to notify us only when the location changes more than a certain amount. Setting up a distance filter can reduce the amount of polling that our application does. Distance filters are also in meters. Specifying a distance filter of 1000 tells the Location Manager not to notify its delegate until the iPhone has moved at least 1000 meters from its previously reported position. As an example:

locationManager.distanceFilter = 1000.0f;

When we're ready to start polling for location, we tell the Location Manager to start, and it will then go off and do its thing and then call a delegate method when it has determined the current location. Until we tell it to stop, it will continue to call our delegate method whenever it senses a change that exceeds the current distance filter. Here is how we start the Location Manager:

 [locationManager startUpdatingLocation];
 

If you need to determine the current location only and have no need to continuously poll for location we should have our location delegate stop the Location Manager as soon as it gets the information our application needs. If we need to continuously poll, make sure stop polling as soon as possible. To tell the Location Manager to stop sending updates to its delegate, call stopUpdatingLocation.

 [locationManager stopUpdatingLocation];
 


13.2 Location Manager Delegate

The Location Manager delegate must conform to the
CLLocationManagerDelegate protocol, which defines two methods, both of which are optional. One of these methods is called by the Location Manager when it has determined the current location or when it detects a change in location. The other method is called when the Location Manager encounters an error.

When the Location Manager wants to inform its delegate of the current location, it calls the
locationManager:didUpdateToLocation:fromLocation: method.
This method has three parameters. The first parameter is the Location Manager that called the method. The second is a CLLocation object that defines the current location of the iPhone, and the third is a CLLocation object that defines the previous location from the last update. The first time this method is called, the previous location object will be nil.

Location information is passed from the Location Manager using instances of the CLLocation class. This class has five properties. The latitude and longitude are stored in a property called coordinate. To get the latitude and longitude in degrees:

CLLocationDegrees latitude = theLocation.coordinate.latitude;
CLLocationDegrees longitude = theLocation.coordinate.longitude;

The CCLocation object can also tell us how confident the Location Manager is in its latitude and longitude calculations. The horizontalAccuracy property describes the radius of a circle with the coordinate as its center. The larger the value in horizontalAccuracy, the less certain Core Location is of the location. A very small radius indicates a hight level of confidence in the determined location.

The CLLocation object also has a property called altitude that can tell us how many meters above or below sea level we are:

CCLocationDistance altitude = theLocation.altitude; 

Each CLLocation object maintains a property called verticalAccuracy that is an indication of how confident Core Location is in its determination of altitude. The value in altitude could be off by as many meters as the value in verticalAccuracy, and if verticalAccuracy value is negative, Core Location is telling us it could not determine a valid altitude.

CCLocation objects also have a timestamp that tells when the Location Manager made the location determination.



13.3 Core Location Application

Time to build an application to detect the iPhone's current location and the total distance traveled while the program has been running.

Let's create a project using the view-based template and name it as myLocation. Here is the header file, "myLocationViewController.h"

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>

@interface myLocationViewController :
UIViewController <CLLocationManagerDelegate> {
    CLLocationManager    *locationManager;
    
    CLLocation           *startingPoint;
    
    UILabel *latitudeLabel;
    UILabel *longitudeLabel;
    UILabel *horizontalAccuracyLabel;
    UILabel *altitudeLabel;
    UILabel *verticalAccuracyLabel;
    UILabel *distanceTraveledLabel;
}
@property (retain, nonatomic) CLLocationManager *locationManager;
@property (retain, nonatomic) CLLocation *startingPoint;
@property (retain, nonatomic) IBOutlet UILabel *latitudeLabel;
@property (retain, nonatomic) IBOutlet UILabel *longitudeLabel;
@property (retain, nonatomic) IBOutlet UILabel *horizontalAccuracyLabel;
@property (retain, nonatomic) IBOutlet UILabel *altitudeLabel;
@property (retain, nonatomic) IBOutlet UILabel *verticalAccuracyLabel;
@property (retain, nonatomic) IBOutlet UILabel *distanceTraveledLabel;
@end

Since Core Location is not part of the UIKit, we needed to include the header files. We also conform this class to the CLLocationManagerDelagate method so that we can retrieve location information from the Location Manager.

Then, we declared a CLLocationManager pointer, which will be used for instance of the Core Location we create. We also declared a pointer to a CLLocation, which we will set to the location we receive in the first update from the Location Manager. The other instance variables are all outlets that will be used to update labels on the user interface.

Let's go to Interface Builder. Put 12 labels to the View window. Then, label them: Latitude:, Longitude:, Horizontal Accuracy:, Altitude:, Vertical Accuracy:, and Distance Traveled:.The six labels on the right size should be connected to the appropriate outlet. After removing the label texts, save nib and back to Xcode.

Outlets

Here is implementation class file, "myLocationViewController.m"

#import "myLocationViewController.h"

@implementation myLocationViewController
@synthesize locationManager;
@synthesize startingPoint;
@synthesize latitudeLabel;
@synthesize longitudeLabel;
@synthesize horizontalAccuracyLabel;
@synthesize altitudeLabel;
@synthesize verticalAccuracyLabel;
@synthesize distanceTraveledLabel;

#pragma mark -

- (void)viewDidLoad {
    self.locationManager = [[CLLocationManager alloc] init];
    locationManager.delegate = self;
    locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    [locationManager startUpdatingLocation];
}

- (void)viewDidUnload {
	// Release any retained subviews of the main view.
	// e.g. self.myOutlet = nil;
    self.locationManager = nil;
    self.latitudeLabel = nil;
    self.longitudeLabel = nil;
    self.horizontalAccuracyLabel = nil;
    self.altitudeLabel = nil;
    self.verticalAccuracyLabel = nil;
    self.distanceTraveledLabel = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [locationManager release];
    [startingPoint release];
    [latitudeLabel release];
    [longitudeLabel release];
    [horizontalAccuracyLabel release];
    [altitudeLabel release];
    [verticalAccuracyLabel release];
    [distanceTraveledLabel release];    
    [super dealloc];
}

#pragma mark -
#pragma mark CLLocationManagerDelegate Methods

- (void)locationManager:(CLLocationManager *)manager 
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation {
    
    if (startingPoint == nil)
        self.startingPoint = newLocation;
    
    NSString *latitudeString = [[NSString alloc] initWithFormat:@"%g°", 
                                newLocation.coordinate.latitude];
    latitudeLabel.text = latitudeString;
    [latitudeString release];
    
    NSString *longitudeString = [[NSString alloc] initWithFormat:@"%g°", 
                                 newLocation.coordinate.longitude];
    longitudeLabel.text = longitudeString;
    [longitudeString release];
    
    NSString *horizontalAccuracyString = [[NSString alloc]
                                          initWithFormat:@"%gm", 
                                          newLocation.horizontalAccuracy];
    horizontalAccuracyLabel.text = horizontalAccuracyString;
    [horizontalAccuracyString release];
    
    NSString *altitudeString = [[NSString alloc] initWithFormat:@"%gm", 
                                newLocation.altitude];
    altitudeLabel.text = altitudeString;
    [altitudeString release];
    
    NSString *verticalAccuracyString = [[NSString alloc]
                                        initWithFormat:@"%gm", 
                                        newLocation.verticalAccuracy];
    verticalAccuracyLabel.text = verticalAccuracyString;
    [verticalAccuracyString release];
    
    CLLocationDistance distance = [newLocation
                                   getDistanceFrom:startingPoint];
    NSString *distanceString = [[NSString alloc]
                                initWithFormat:@"%gm", distance];
    distanceTraveledLabel.text = distanceString;
    [distanceString release];
}

- (void)locationManager:(CLLocationManager *)manager 
       didFailWithError:(NSError *)error {
    
    NSString *errorType = (error.code == kCLErrorDenied) ? 
    @"Access Denied" : @"Unknown Error";
    UIAlertView *alert = [[UIAlertView alloc] 
                          initWithTitle:@"Error getting Location" 
                          message:errorType 
                          delegate:nil 
                          cancelButtonTitle:@"Okay" 
                          otherButtonTitles:nil];
    [alert show];
    [alert release];
    
}
@end

Build and Run.

LocationResult