Building Location-Based Applications for the iPhone

owadays, it is becoming common to find mobile devices equipped with GPS receivers. Using a GPS receiver, you can pinpoint your current location easily via the many satellites orbiting around earth courtesy of the US government. However, GPS receivers require a clear line-of-sight to the sky to work, so they tend to work poorly or not at all indoors.

Another effective way to locate one’s position is through cell tower triangulation. When a mobile phone is on, it stays in constant contact with base stations within range. If you know the identity of these cell towers, it is possible to calculate a phone’s physical location by using various databases that contain the cell towers’ identity and their exact geographical location. Cell tower triangulation doesn’t need satellites, so unlike GPS, it works indoors. However, it is not as accurate as GPS, because its accuracy depends on how closely cell towers are spaced in the area you are in—it works best in densely populated areas where the cell towers are close together.

Author’s Note: The first generation iPhone was not equipped with a GPS receiver, and cell tower triangulation does not apply to the iPod Touch because it’s not a cellular phone.

Yet a third method is to rely on Wi-Fi triangulation. Using this method, the device connects to a Wi-Fi network and checks the service provider against databases to determine the location serviced by the provider. There’s no triangulation and no satellites involved, so this method works wherever the device can connect to a Wi-Fi network, but it’s also the least accurate of the three methods.

Core Location Framework

On the iPhone, Apple provides the Core Location framework to help you determine your physical location. The beauty of this framework is that it makes use of all three approaches mentioned here; which method it actually uses at any given time is totally transparent to developers, who need only specify the accuracy they need, and Core Location will go about determining the best way to obtain those results.

Sound amazing? It is. The rest of this article shows how you can do this in code.

Obtaining Location Coordinates

Using Xcode, create a new View-based Application project and name it LBS. In the new project, double-click on the LBSViewController.xib file to edit it in Interface Builder.

Populate the View window with the following views (see Figure 1):

 
Figure 1. Example Location View: Populate the View window with TextField and Label views.
  • Label
  • TextField

Right-click on the Frameworks group in Xcode and select Add ? Existing Frameworks…. Select Framework/CoreLocation.framework.

Add the statements in bold text in the following code to the LBSViewController.h file:

#import #import @interface LBSViewController : UIViewController      {    IBOutlet UITextField *latitudeTextField;    IBOutlet UITextField *longitudeTextField;    IBOutlet UITextField *accuracyTextField;    CLLocationManager *lm;}@property (retain, nonatomic) UITextField *latitudeTextField;@property (retain, nonatomic) UITextField *longitudeTextField;@property (retain, nonatomic) UITextField *accuracyTextField;@end

To use the CLLocationManager class, you need to implement the CLLocationManagerDelegate protocol in your view controller class. You also need to create three outlets that you’ll connect to the three TextField views in the View window.

Back in Interface Builder, control-click and drag the File’s Owner item to each of the three TextField views, then select latitudeTextField, longitudeTextField, and accuracyTextField, respectively.

Insert the statements shown in bold text below into the LBSViewController.m file:

#import "LBSViewController.h"@implementation [email protected] latitudeTextField, longitudeTextField, accuracyTextField;- (void) viewDidLoad {    lm = [[CLLocationManager alloc] init];    if ([lm locationServicesEnabled]) {        lm.delegate = self;        lm.desiredAccuracy = kCLLocationAccuracyBest;        lm.distanceFilter = 1000.0f;        [lm startUpdatingLocation];    }}- (void) locationManager: (CLLocationManager *) manager    didUpdateToLocation: (CLLocation *) newLocation    fromLocation: (CLLocation *) oldLocation{    NSString *lat = [[NSString alloc] initWithFormat:@"%g",         newLocation.coordinate.latitude];    latitudeTextField.text = lat;        NSString *lng = [[NSString alloc] initWithFormat:@"%g",         newLocation.coordinate.longitude];    longitudeTextField.text = lng;        NSString *acc = [[NSString alloc] initWithFormat:@"%g",         newLocation.horizontalAccuracy];    accuracyTextField.text = acc;            [acc release];    [lat release];    [lng release];}- (void) locationManager: (CLLocationManager *) manager    didFailWithError: (NSError *) error {    NSString *msg = [[NSString alloc]        initWithString:@"Error obtaining location"];    UIAlertView *alert = [[UIAlertView alloc]                          initWithTitle:@"Error"                           message:msg                           delegate:nil                           cancelButtonTitle: @"Done"                          otherButtonTitles:nil];    [alert show];        [msg release];    [alert release];}- (void) dealloc{    [lm release];    [latitudeTextField release];    [longitudeTextField release];    [accuracyTextField release];    [super dealloc];}

The preceding code creates an instance of the CLLocationManager class when the View is loaded. Before using the object, you should check to see whether the user has enabled location services on the device. If so, you can then proceed to specify the desired accuracy using the desiredAccuracy property. Use the following constants to specify the accuracy that you want:

  • kCLLocationAccuracyBest
  • kCLLocationAccuracyNearestTenMeters
  • kCLLocationAccuracyHundredMeters
  • kCLLocationAccuracyKilometer
  • kCLLocationAccuracyThreeKilometers

You should note that while you’re perfectly free to specify the best accuracy all the time, the actual accuracy—whatever you select—is not guaranteed. In addition, specifying a location with greater accuracy than you need takes up significant device time and battery power.

The distanceFilter property allows you to specify the distance a device must move laterally before it should generate a location update. The unit for this property is in meters, relative to its last position. If you want to be notified of all movements, use the kCLDistanceFilterNone constant. Finally, you start the location manager using the startUpdatingLocation method.

 
Figure 2. Location Test: When you test the sample application on the iPhone Simulator, you’ll always get these (fixed) values.

To obtain location information, you need to handle two events:

  • locationManager:didUpdateToLocation:fromLocation:
  • locationManager:didFailWithError:

When a new location value is available, the device fires the locationManager:didUpdateToLocation:fromLocation: event. If the location manager cannot determine a location value, it will fire the locationManager:didFailWithError: event.

When the device can determine a location, you want to display its latitude, longitude, and accuracy, which you do using the CLLocation object. This object’s horizontalAccuracy property specifies the radius of accuracy, in meters.

Press Command-r to test the application on the iPhone Simulator. Figure 2 shows the simulator displaying the latitude and longitude of the location returned. It also shows the accuracy of the result.

Author’s Note: For the simulator, the device always reports a fixed position. There is no prize for guessing correctly where this position refers to.

Displaying Maps

While obtaining the location coordinates of a position is interesting, such information is much more useful if you can visually locate it on a map. Fortunately, the iPhone SDK 3.0 ships with the Map Kit API, which makes displaying Google Maps in your application a snap. Here’s an example.

Using the same project you created in the previous section, add a Button view to the View window in the LBSViewController.xib file (see Figure 3).

 
Figure 3. View Map Button: Here’s how the sample project looks after adding a “View Map” Button view to the View window.

Right-click on the Frameworks group in Xcode and add a new framework called MapKit.framework.

Add the statement shown below in bold to the LBSViewController.h file:

#import #import #import @interface LBSViewController : UIViewController      {    IBOutlet UITextField *accuracyTextField;    IBOutlet UITextField *latitudeTextField;    IBOutlet UITextField *longitudeTextField;    CLLocationManager *lm;      MKMapView *mapView;}@property (retain, nonatomic) UITextField *accuracyTextField;@property (retain, nonatomic) UITextField *latitudeTextField;@property (retain, nonatomic) UITextField *longitudeTextField;-(IBAction) btnViewMap: (id) sender;@end

Back in Interface Builder, control-click, and drag the Button view to the File’s Owner item, and then select btnViewMap:.

In the LBSViewController.m file, add the statements in bold from the following code:

-(IBAction) btnViewMap: (id) sender {    [self.view addSubview:mapView];}- (void) viewDidLoad {    lm = [[CLLocationManager alloc] init];    lm.delegate = self;    lm.desiredAccuracy = kCLLocationAccuracyBest;    lm.distanceFilter = 1000.0f;    [lm startUpdatingLocation];        mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];    mapView.mapType = MKMapTypeHybrid; }- (void) locationManager: (CLLocationManager *) manager    didUpdateToLocation: (CLLocation *) newLocation    fromLocation: (CLLocation *) oldLocation{    NSString *lat = [[NSString alloc] initWithFormat:@"%g",         newLocation.coordinate.latitude];    latitudeTextField.text = lat;        NSString *lng = [[NSString alloc] initWithFormat:@"%g",         newLocation.coordinate.longitude];    longitudeTextField.text = lng;        NSString *acc = [[NSString alloc] initWithFormat:@"%g",         newLocation.horizontalAccuracy];    accuracyTextField.text = acc;            [acc release];    [lat release];    [lng release];        MKCoordinateSpan span;    span.latitudeDelta=.005;    span.longitudeDelta=.005;        MKCoordinateRegion region;    region.center = newLocation.coordinate;    region.span=span;        [mapView setRegion:region animated:TRUE]; }- (void) dealloc{    [mapView release];    [lm release];    [latitudeTextField release];    [longitudeTextField release];    [accuracyTextField release];    [super dealloc];}

 
Figure 4. Mapped Location: Display the map of the location reported by Core Location framework.

Basically, the preceding code:

  • Creates an instance of the MKMapView class when the view has loaded, and sets the map type (hybrid: map and satellite in this case) to display.
  • Adds the mapView object to the current view when a user taps the View Map button.
  • Zooms into the location using the mapView object’s setRegion: method whenever the location information gets updated.

Press Command-r to test the application on the iPhone Simulator. Now, tapping on the View Map button will show a map containing the location reported by the location manager (see Figure 4).

Because the simulator always provides the same fixed location, this is as far as you can go without testing on a real device. When you do that, you’ll be able to see the map update itself dynamically as you move the device around. Be sure to alter the distanceFilter property to a smaller number so that you will be able to track small changes in distance.

As you’ve seen, the iPhone SDK’s Core Location framework makes it easy to implement location-based services. In addition, the MapKit (also in the iPhone 3.0 SDK) makes displaying maps a snap. If you have not yet had an opportunity to build a location-based application, you’re coming to the field at the right time. With all these new features, now is the perfect time to begin!

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts