Core Location / MapKit



Relevant resources


Documentation


Video


Podcast


Books


Sample code - Apple


Sample code - class


Core Location


All iPhone OS devices have some means of determining their current location.  iPhone 3G and 3G S devices have built-in GPS functionality, but the others either use cell tower triangulation or WiFi access point detection to determine a cruder location.  Core Location is the framework that lets you find out where the device is currently located.


Core Location abstracts away the details of the device's hardware, and it attempts to deliver position information to the precision you specify.


To use Core Location within your project, you will first need to import the appropriate header:


#import <CoreLocation/CoreLocation.h>


and link the CoreLocation framework with your project.


Location manager


To receive location updates, you first need to create and configure an instance of CLLocationManager.  You could make this an instance variable on the view controller where location information will be processed, on the application delegate, or a singleton for use application-wide.  To create a location manager, you would use code like the following:


locationManager = [[CLLocationManager alloc] init];

locationManager.delegate = self;


The second line sets the location manager's delegate.  The delegate receives notifications about the current position or heading and must conform to the CLLocationManagerDelegate protocol.  The first of the notifications you receive is 


- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation


which lets you know when the position reported by the location manager has changed.  This will be your most frequently used delegate method when handling location information.


The new and old locations passed in to this method are CLLocation objects.  These objects encapsulate a lot of position-related data within themselves, including position, altitude, time the reading was taken, etc.  These data are available through the various properties on CLLocation, which are:



CLLocations can be created by specifying latitude and longitude with 


- (id)initWithLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude


or the full set of parameters with


- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate altitude:(CLLocationDistance)altitude horizontalAccuracy:(CLLocationAccuracy)hAccuracy verticalAccuracy:(CLLocationAccuracy)vAccuracy timestamp:(NSDate *)timestamp


As a convenience, the distance between two CLLocations can be found by using the -getDistanceFrom: method.


Another delegate method related to position is


- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error


which reports back any errors happening during the location finding process.  These errors include kCLErrorLocationUnknown, which happens if a location fix can't be made at the current time, kCLErrorDenied if the user denies your application the ability to use location information, and kCLErrorHeadingFailure if something is interfering with the compass (on the 3G S).


The iPhone 3G S can report magnetic heading information using the delegate method


- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading


The CLHeading object passed into this method contains 



Finally, the delegate method


- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager


allows for a calibration dialog to appear for the user to calibrate the compass in the device.


Note that if you do any location testing in the iPhone Simulator, your position will always be reported as Apple's headquarters in Cupertino, CA.


MapKit


When dealing with location-based data, finding your current position is just one piece in the puzzle.  At some point, you will need to display this and other positions, as well as other map-based data.  In iPhone OS 3.0, Apple introduced MapKit to make this easy.  MapKit provides native controls for pulling down Google Maps data and displaying maps and annotations.  


Because the Google Maps API is used to download map tiles for display, your iPhone application must comply with the terms of service for this API.  These terms can be found at http://code.google.com/apis/maps/iphone/terms.html .


The core element in MapKit is the MKMapView.  MKMapView displays a map and allows annotation and interaction with it, providing street, satellite, and hybrid maps.  You can create and add a MKMapView to your application as you would any other UIView subclass, as well as do so from Interface Builder.


Before using MapKit in your application, you will need to include the proper header


#import <MapKit/MapKit.h>


and make sure that the MapKit and CoreLocation frameworks are linked into your application.


Once you have created a MKMapView instance and added it as a subview (either in code or via Interface Builder), you can configure the map view using several properties.


mapView.zoomEnabled = YES;


will let users pinch-zoom to change the scale of the map.  This defaults to YES, but you can turn it off using a setting of NO.


mapView.scrollEnabled = YES;


will let users scroll the map around.  Again, this defaults to YES and can be disabled with a setting of NO.


mapView.showsUserLocation = YES;


will cause the MKMapView to use Core Location to find the user's current location and display it on the map as a blue dot.  This location will continually update until you set this property to NO.  The current location can be extracted using the userLocation property on the map view, although you'll need to use the location property on that to get the CLLocation for the current position.


To set the region displayed within the map view, you can use the region property (which changes the region that is displayed instantaneously) or the -setRegion:animated: method (which lets you animate the change of a region on the map).  This region is a MKCoordinateRegion structure, which contains a CLLocationCoordinate2D struct for its center element and a MKCoordinateSpan struct that describes the latitude and longitude span for the region.  For example, to set the region on a map view to show the region containing two points, you could use code like the following:


CLLocationCoordinate2D currentCoordinate = mapView.userLocation.location.coordinate;

CLLocationCoordinate2D matcCoordinate = matcLocationAnnotation.coordinate;


MKCoordinateRegion regionToDisplay;

regionToDisplay.span.latitudeDelta = abs(matcCoordinate.latitude - currentCoordinate.latitude);

regionToDisplay.span.longitudeDelta = abs(matcCoordinate.longitude - currentCoordinate.longitude);

regionToDisplay.center.latitude = (matcCoordinate.latitude + currentCoordinate.latitude) / 2.0;

regionToDisplay.center.longitude = (matcCoordinate.longitude + currentCoordinate.longitude) / 2.0;

[mapView setRegion:regionToDisplay animated:YES];


Annotations (the pins that appear on the map) can be added using -addAnnotation:.  These annotations need to be objects that conform to the MKAnnotation protocol, so they can be a simple NSObject subclass or one of your model objects (even an NSManagedObject subclass that you store via Core Data).  The MKAnnotation protocol requires you to implement the following methods:


- (CLLocationCoordinate2D) coordinate

- (NSString *)subtitle

- (NSString *)title


These provide the geographical location for the annotation, as well as a title and subtitle to be displayed in the callout for that annotation.


A MKMapView can be customized using a delegate that conforms to the MKMapViewDelegate protocol.  The methods that this delegate can provide are


- (void)mapViewWillStartLoadingMap:(MKMapView *)mapView

- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView

- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error


These let you respond when the map starts and stops loading new map tiles, as well as when that loading fails (most likely due to a slow or nonexistant network connection).  You might use these methods to display a loading indicator while downloading new map tiles.


- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated


These let you prepare for and handle changes in the region displayed in the map view.  You might use this to change an overlay that you have placed on top of a map.


- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views


This lets you do specific actions in response to annotations being dropped onto the map.


Map views give you the ability to customize the views used for annotations, which are normally red pins that display a label when tapped on.  These views are provided in a manner similar to the cells of a UITableView, where they are cached and reused based on an identifier.  You can start from a standard MKPinAnnotationView and customize elements of that, or do a custom MKAnnotationView.  The following is an example of how to customize one of these annotations:


- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation

{

MKPinAnnotationView *view = nil;

if (annotation != theMapView.userLocation)

{

view = (MKPinAnnotationView *)[theMapView dequeueReusableAnnotationViewWithIdentifier:@"MATCLocationPinView"];

if (view == nil)

{

view = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"MATCLocationPinView"] autorelease];

view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];

[view setPinColor:MKPinAnnotationColorGreen];

[view setCanShowCallout:YES];

[view setAnimatesDrop:YES];

}

}

return view;

}


In this example, the pin color is changed from the default red to green, a disclosure arrow is added to the pop-up that appears over the annotation, and we make it so the pin drops onto the map in an animated fashion.  This annotation style is not applied to the current user position, so that we can use the default blue dot for that.


The accessory view you added to the the annotation callout can be tapped on, at which point the following delegate method is called:


- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control


As you can see, map views are easy to set up and use, for the kind of functionality they provide.


Work-at-home project:


Using Core Location, let the user capture their current location and store it locally (with a title, if possible).  Core Data would make this data storage easy.  Add in the capability for the user to see any or all of these saved points on a map.