Chapter     19

Where Am I? Finding Your Way with Core Location and Map Kit

Every iOS device has the ability to determine where in the world it is using a framework called Core Location. iOS also includes a framework called Map Kit that lets you easily create a live interactive map showing any locations you like, including of course the user’s location. In this chapter we’ll get you started using both of these frameworks.

Core Location can actually leverage three technologies to do this: GPS, cell ID location, and Wi-Fi Positioning Service (WPS). GPS is the most accurate of the three technologies, but it is not available on first-generation iPhones, iPod touches, or Wi-Fi-only iPads. In short, any device with at least a 3G data connection also contains a GPS unit. GPS reads microwave signals from multiple satellites to determine the current location.

Note  Technically, Apple uses a version of GPS called Assisted GPS, also known as A-GPS. A-GPS uses network resources to help improve the performance of stand-alone GPS. The basic idea is that the telephony provider deploys services on its network that mobile devices will automatically find and collect some data from. This allows a mobile device to determine its starting location much more quickly than if it were relying on the GPS satellites alone.

Cell ID location lookup gives a rough approximation of the current location based on the physical location of the cellular base station that the device is currently in contact with. Since each base station can cover a fairly large area, there is a fairly large margin of error here. Cell ID location lookup requires a cell radio connection, so it works only on the iPhone (all models, including the very first) and any iPad with a 3G data connection.

The WPS option uses the MAC addresses from nearby Wi-Fi access points to make a guess at your location by referencing a large database of known service providers and the areas they service. WPS is imprecise and can be off by many miles.

All three methods put a noticeable drain on the battery, so keep that in mind when using Core Location. Your application shouldn’t poll for location any more often than is absolutely necessary. When using Core Location, you have the option of specifying a desired accuracy. By carefully specifying the absolute minimum accuracy level you need, you can prevent unnecessary battery drain.

The technologies that Core Location depends on are hidden from your application. We don’t tell Core Location whether to use GPS, triangulation, or WPS. We just tell it how accurate we would like it to be, and it will decide from the technologies available to it which is best for fulfilling our request.

The Location Manager

The Core Location API is actually fairly easy to use. The main class we’ll work with is CLLocationManager, usually referred to as the location manager. To interact with Core Location, you need to create an instance of the location manager, like this:

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

This creates an instance of the location manager, but it doesn’t actually start polling for your location. You must create an object that conforms to the CLLocationManagerDelegate protocol and assign it as the location manager’s delegate. The location manager will call delegate methods when location information becomes available or changes. The process of determining location may take some time—even a few seconds.

Setting the Desired Accuracy

After you set the delegate, you also want to set the desired accuracy. As we mentioned, don’t specify a degree of accuracy any greater than you absolutely need. If you’re writing an application that just needs to know which state or country the phone is in, don’t specify a high level of precision. Remember that the more accuracy you demand of Core Location, the more juice you’re likely to use. Also, keep in mind that there is no guarantee that you will get the level of accuracy you have requested.

Here’s an example of setting the delegate and requesting a specific level of accuracy:

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

The accuracy is set using a CLLocationAccuracy value, a type that’s defined as a double. The value is in meters, so if you specify a desiredAccuracy of 10, you’re telling Core Location that you want it to try to determine the current location within 10 meters, if possible. Specifying kCLLocationAccuracyBest (as we did previously) or specifying kCLLocationAccuracyBestForNavigation (where it uses other sensor data as well) tells Core Location to use the most accurate method that’s currently available. In addition, you can also use kCLLocationAccuracyNearestTenMeters, kCLLocationAccuracyHundredMeters, kCLLocationAccuracyKilometer, and kCLLocationAccuracyThreeKilometers.

Setting the Distance Filter

By default, the location manager will notify the delegate of any detected change in the device’s location. By specifying a distance filter, you are telling the location manager not to notify you of every change, but instead to notify you only when the location changes by more than a certain amount. Setting up a distance filter can reduce the amount of polling your application does.

Distance filters are also set in meters. Specifying a distance filter of 1000 tells the location manager not to notify its delegate until the iPhone has moved at least 1,000 meters from its previously reported position. Here’s an example:

locationManager.distanceFilter = 1000;

If you ever want to return the location manager to the default setting, which applies no filter, you can use the constant kCLDistanceFilterNone, like this:

locationManager.distanceFilter = kCLDistanceFilterNone;

Just as when specifying the desired accuracy, you should take care to avoid getting updates any more frequently than you really need them; otherwise, you waste battery power. A speedometer app that’s calculating the user’s velocity based on the user’s location will probably want to have updates as quickly as possible, but an app that’s going to show the nearest fast-food restaurant can get by with a lot fewer updates.

Starting the Location Manager

When you’re ready to start polling for location, you tell the location manager to start. It will go off and do its thing and then call a delegate method when it has determined the current location. Until you tell it to stop, it will continue to call your delegate method whenever it senses a change that exceeds the current distance filter.

Here’s how you start the location manager:

[locationManager startUpdatingLocation];

Using the Location Manager Wisely

If you need to determine the current location only and have no need to continuously poll for location, you should have your location delegate stop the location manager as soon as it gets the information your application requires. If you need to continuously poll, make sure you stop polling as soon as you possibly can. Remember that as long as you are getting updates from the location manager, you are putting a strain on the user’s battery.

To tell the location manager to stop sending updates to its delegate, call stopUpdatingLocation, like this:

[locationManager stopUpdatingLocation];

The Location Manager Delegate

The location manager delegate must conform to the CLLocationManagerDelegate protocol, which defines several methods, all of them 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. Another method is called when the location manager encounters an error. We’ll implement both of these in our app.

Getting Location Updates

When the location manager wants to inform its delegate of the current location, it calls the locationManager:didUpdateLocations: method. This method takes two parameters:

  • The first parameter is the location manager that called the method.
  • The second parameter is an array of CLLocation objects that describe the current location of the device and perhaps a few previous locations. If several location updates occur in a short period of time, they may be reported all at once with a single call to this method. In any case, the most recent location is always the last item in this array.

Getting Latitude and Longitude Using CLLocation

Location information is passed from the location manager using instances of the CLLocation class. This class has five properties that might be of interest to your application:

  • coordinate
  • horizontalAccuracy
  • altitude
  • verticalAccuracy
  • timestamp

The latitude and longitude are stored in a property called coordinate. To get the latitude and longitude in degrees, do this:

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

The CLLocation object can also tell you 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 high level of confidence in the determined location.

You can see a graphic representation of horizontalAccuracy in the Maps application (see Figure 19-1). The circle shown in Maps uses horizontalAccuracy for its radius when it detects your location. The location manager thinks you are at the center of that circle. If you’re not, you’re almost certainly somewhere inside the circle. A negative value in horizontalAccuracy is an indication that you cannot rely on the values in coordinate for some reason.

9781430260226_Fig19-01.jpg

Figure 19-1. The Maps application uses Core Location to determine your current location. The outer circle is a visual representation of the horizontal accuracy

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

CLLocationDistance 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. If the verticalAccuracy value is negative, Core Location is telling you it could not determine a valid altitude.

CLLocation objects also have a timestamp that tells when the location manager made the location determination.

In addition to these properties, CLLocation has a useful instance method that will let you determine the distance between two CLLocation objects. The method is called distanceFromLocation: and it works like this:

CLLocationDistance distance = [fromLocation distanceFromLocation:toLocation];

The preceding line of code will return the distance between two CLLocation objects: fromLocation and toLocation. This distance value returned will be the result of a great-circle distance calculation that ignores the altitude property and calculates the distance as if both points were at sea level. For most purposes, a great-circle calculation will be more than sufficient; however, if you do want to take altitude into account when calculating distances, you’ll need to write your own code to do it.

Note  If you’re not sure what’s meant by great-circle distance, you might want to think back to geography class and the notion of a great-circle route. The idea is that the shortest distance between any two points on the earth’s surface will be found along a path that would, if extended, go the entire way around the earth: a “great circle.” The most obvious great circles are perhaps the ones you’ve seen on maps: The equator, and the longitudinal lines. However, such a circle can be found for any two points on the surface of the earth. The calculation performed by CLLocation determines the distance between two points along such a route, taking the curvature of the earth into account. Without accounting for that curvature, you would end up with the length of a straight line connecting the two points, which isn’t much use, since that line would invariably go straight through some amount of the earth itself!

Error Notifications

If Core Location is not able to determine your current location, it will call a second delegate method named locationManager:didFailWithError:. The most likely cause of an error is that the user denies access. The user must authorize use of the location manager, so the first time your application wants to determine the location, an alert will pop up on the screen asking if it’s OK for the current program to access your location (see Figure 19-2).

9781430260226_Fig19-02.jpg

Figure 19-2. Location manager access must be approved by the user

If the user taps the Don’t Allow button, your delegate will be notified of the fact by the location manager using the locationManager:didFailWithError: with an error code of kCLErrorDenied. Another commonly encountered error code supported by the location manager is kCLErrorLocationUnknown, which indicates that Core Location was unable to determine the location but that it will keep trying. While a kCLErrorLocationUnknown error indicates a problem that may be temporary, kCLErrorDenied and other errors generally indicate that your application will not be able to access Core Location any time during the remainder of the current session.

Note  When working in the simulator, a dialog will appear outside the simulator window, asking to use your current location. In that case, your location will be determined using a super-secret algorithm kept in a locked vault buried deep beneath Apple headquarters in Cupertino.

Trying Out Core Location

Let’s build a small application to detect the iPhone’s current location and the total distance traveled while the program has been running. You can see what the first version of our application will look like in Figure 19-3.

9781430260226_Fig19-03.jpg

Figure 19-3. The WhereAmI application in action

In Xcode, create a new project using the Single View Application template and call it WhereAmI. Next, set Device Family to iPhone, select BIDViewController.h, and make the following changes:

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
 
@interface BIDViewController :
    UIViewController <CLLocationManagerDelegate>
 
@end

First, notice that we’ve included the Core Location header files. Core Location is not part of either UIKit or Foundation, so we need to include the header files manually. Next, we conform this class to the CLLocationManagerDelegate method, so that we can receive location information from the location manager.

Now select BIDViewController.m and add these property declarations to the class extension near the top of the file:

#import "BIDViewController.h"
 
@interface BIDViewController ()
 
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) CLLocation *previousPoint;
@property (assign, nonatomic) CLLocationDistance totalMovementDistance;
@property (weak, nonatomic) IBOutlet UILabel *latitudeLabel;
@property (weak, nonatomic) IBOutlet UILabel *longitudeLabel;
@property (weak, nonatomic) IBOutlet UILabel *horizontalAccuracyLabel;
@property (weak, nonatomic) IBOutlet UILabel *altitudeLabel;
@property (weak, nonatomic) IBOutlet UILabel *verticalAccuracyLabel;
@property (weak, nonatomic) IBOutlet UILabel *distanceTraveledLabel;
 
@end

First, we declare a CLLocationManager pointer, which will be used to hold a pointer to the instance of the Core Location Manager we’re going create. We also declare a pointer to a CLLocation, which we will set to the location of the last update we received from the location manager. This way, each time the user moves far enough to trigger update, we’ll be able to add the latest movement distance to our running total.

The remaining properties are all outlets that will be used to update labels on the user interface.

Select Main.storyboard to create the GUI. Using Figure 19-3 as your guide, drag 12 Labels from the library to the View window. Six of them should be placed on the left side of the screen, right-justified, and made bold. Give the six bold labels the values Latitude:, Longitude:, Horizontal Accuracy:, Altitude:, Vertical Accuracy:, and Distance Traveled:. Since the Horizontal Accuracy: label is the longest, you might place that one first, and then Option-drag copies of that label to create the other five left-side labels. The six right-side labels should be left-justified and placed next to each of the bold labels.

Each of the labels on the right side should be connected to the appropriate outlet we defined in the header file earlier. Once you have all six attached to outlets, double-click each one in turn, and delete the text it holds. Save your changes.

Next, select BIDViewController.m and insert the following lines in viewDidLoad to configure the location manager:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    [self.locationManager startUpdatingLocation];
}

In the viewDidLoad method, we allocate and initialize a CLLocationManager instance, assign our controller class as the delegate, set the desired accuracy to the best available, and then tell our location manager instance to start giving us location updates.

Now insert the following new delegate methods at the end of the @implementation block to handle information received from the location manager:

#pragma mark - CLLocationManagerDelegate Methods
- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray *)locations {
    CLLocation *newLocation = [locations lastObject];
    NSString *latitudeString = [NSString stringWithFormat:@"%gu00B0",
                                newLocation.coordinate.latitude];
    self.latitudeLabel.text = latitudeString;
    
    NSString *longitudeString = [NSString stringWithFormat:@"%gu00B0",
                                 newLocation.coordinate.longitude];
    self.longitudeLabel.text = longitudeString;
    
    NSString *horizontalAccuracyString = [NSString stringWithFormat:@"%gm",
                                          newLocation.horizontalAccuracy];
    self.horizontalAccuracyLabel.text = horizontalAccuracyString;
    
    NSString *altitudeString = [NSString stringWithFormat:@"%gm",
                                newLocation.altitude];
    self.altitudeLabel.text = altitudeString;
    
    NSString *verticalAccuracyString = [NSString stringWithFormat:@"%gm",
                                        newLocation.verticalAccuracy];
    self.verticalAccuracyLabel.text = verticalAccuracyString;
    
    if (newLocation.verticalAccuracy < 0 ||
        newLocation.horizontalAccuracy < 0) {
        // invalid accuracy
        return;
    }
    
    if (newLocation.horizontalAccuracy > 100 ||
        newLocation.verticalAccuracy > 50) {
        // accuracy radius is so large, we don't want to use it
        return;
    }
    
    if (self.previousPoint == nil) {
        self.totalMovementDistance = 0;
    } else {
        self.totalMovementDistance += [newLocation
                                  distanceFromLocation:self.previousPoint];
    }
    self.previousPoint = newLocation;
    
    NSString *distanceString = [NSString stringWithFormat:@"%gm",
                                self.totalMovementDistance];
    self.distanceTraveledLabel.text = distanceString;
    
}
 
- (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];
}

Updating Location Manager

Since this class designated itself as the location manager’s delegate, we know that location updates will come into this class if we implement the delegate method locationManager:didUpdateLocations:. Now, let’s look at our implementation of that method.

The first thing we do in the delegate method is update the first five labels with values from the CLLocation object passed in the newLocation argument:

NSString *latitudeString = [NSString stringWithFormat:@"%gu00B0",
                            newLocation.coordinate.latitude];
self.latitudeLabel.text = latitudeString;
 
NSString *longitudeString = [NSString stringWithFormat:@"%gu00B0",
                             newLocation.coordinate.longitude];
self.longitudeLabel.text = longitudeString;
 
NSString *horizontalAccuracyString = [NSString stringWithFormat:@"%gm",
                                      newLocation.horizontalAccuracy];
self.horizontalAccuracyLabel.text = horizontalAccuracyString;
 
NSString *altitudeString = [NSString stringWithFormat:@"%gm",
                            newLocation.altitude];
self.altitudeLabel.text = altitudeString;
 
NSString *verticalAccuracyString = [NSString stringWithFormat:@"%gm",
                                    newLocation.verticalAccuracy];
self.verticalAccuracyLabel.text = verticalAccuracyString;

Note  Both the longitude and latitude are displayed in formatting strings containing the cryptic-looking u00B0. This is the hexadecimal value of the Unicode representation of the degree symbol (°). It’s never a good idea to put anything other than ASCII characters directly in a source code file, but including the hex value in a string is just fine, and that’s what we’ve done here.

Next, we check the accuracy of the values that the location manager gives us. Negative accuracy values indicate that the location is actually invalid, while high accuracy values indicate that the location manager isn’t quite sure about the location. These accuracy values are in meters and indicate the radius of a circle from the location we’re given, meaning that the true location could be anywhere in that circle. Our code checks to see whether these values are acceptably accurate; if not, it simply returns from this method rather than doing anything more with garbage data:

if (newLocation.verticalAccuracy < 0 ||
    newLocation.horizontalAccuracy < 0) {
    // invalid accuracy
    return;
}
 
if (newLocation.horizontalAccuracy > 100 ||
    newLocation.verticalAccuracy > 50) {
    // accuracy radius is so large, we don't want to use it
    return;
}

Next, we check whether previousPoint is nil. If it is, then this update is the first valid one we’ve gotten from the location manager, so we zero out the distanceFromStart property. Otherwise, we add the latest location’s distance from the previous point to the total distance. In either case, we update previousPoint to contain the current location:

if (self.previousPoint == nil) {
    self.totalMovementDistance = 0;
} else {
    self.totalMovementDistance += [newLocation
                              distanceFromLocation:self.previousPoint];
}
self.previousPoint = newLocation;

After that, we populate the final label with the total distance that we’ve traveled from the start point. While this application runs, if the user moves far enough for the location manager to detect the change, the Distance Traveled: field will be continually updated with the distance the user has moved since the application started:

NSString *distanceString = [NSString stringWithFormat:@"%gm",
                            self.totalMovementDistance];
self.distanceTraveledLabel.text = distanceString;

And there you have it. Core Location is fairly straightforward and easy to use.

Compile and run the application, and then try it. If you have the ability to run the application on your iPhone or iPad, try going for a drive with the application running and watch the values change as you drive. Um, actually, it’s better to have someone else do the driving!

Visualizing your Movement on a Map

What we’ve done so far is pretty neat, but wouldn’t it be nice if we could visualize our travel on a map? Fortunately, iOS includes the Map Kit framework to help us out here. Map Kit utilizes the same back-end services that Apple’s Maps app uses, which means it’s fairly robust and improving all the time. It contains one primary view class representing a map display, and it responds to user gestures just as you’d expect of any modern mapping app. This view also lets us insert annotations for any locations we want to show up on our map, which by default show up as “pins” that can be touched to reveal some more info. We’re going to extend our WhereAmI app to display the user’s starting position and current position on a map.

Start off by selecting BIDViewController.h in Xcode. Add the following near the top to import the mapping framework headers:

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

Next, switch to BIDViewController.m and add a new property declaration below the others in the class extension:

@property (weak, nonatomic) IBOutlet MKMapView *mapView;

Now select Main.storyboard to edit the view. We’d like to keep all of our labels as they are, but we need to make them pop out from the backdrop of the map view in some way. A semitransparent box in the form of a UIView will do nicely. So, select all the labels, and then choose Editor image Embed In image View from the menu.

Make sure the new view is selected, and then use the attributes inspector to disable the User Interaction Enabled checkbox (so that touching anything in this view will be ignored, passing all touch events to the map view instead) and to set its background color to something partly transparent. Drag the view to the bottom of its superview. Now we need to create some constraints so that this box will keep both its position at the bottom and its vertical size, no matter how the screen size changes. So, click the Pin button at the bottom of the editor area to bring up the constraint creation panel. In the upper section of this panel, click the checkboxes to the left, right, and bottom of the small square. In the middle part, click the Height checkbox. When you find yourself looking at something like Figure 19-4, click the Add 4 Constraints button at the bottom:

9781430260226_Fig19-04.jpg

Figure 19-4. All our labels are now boxed in and shifted down. Setting these constraints will make this view stay put

Now find an MKMapView in the object library and drag it into your main view. Use the resize handles to make it fill the entire view, and then select Editor image Arrange image Send to Back to make it appear behind the pre-existing labels. Hook up the map view by control-dragging from the View Controller to the map view and selecting the mapView outlet. Create constraints for the map view using the Pin panel again, this time clicking all four checkboxes surrounding the small box in the upper portion before clicking the Add 4 Constraints button.

Now that these preliminaries are in place, it’s time to write a little code that will make this map do some work for us. Before dealing with the controller, we need to set up a sort of model class to represent our starting point. MKMapView is built as the View part of an MVC architecture, and it works best if we have distinct classes to represent markers on the map. We can pass model objects off to the map view, and it will query them for coordinates, a title, and so on using a protocol defined in the Map Kit framework.

Make a new Objective-C class in Xcode by subclassing from NSObject and naming it BIDPlace. Select BIDPlace.h and modify it as shown here. You need to import the Map Kit header, specify a protocol the new class conforms to, and add some properties:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
 
@interface BIDPlace : NSObject<MKAnnotation>
 
@property (copy, nonatomic) NSString *title;
@property (copy, nonatomic) NSString *subtitle;
@property (assign, nonatomic) CLLocationCoordinate2D coordinate;
 
@end

This is a fairly “dumb” class that acts solely as a holder for these properties. We don’t even need to touch the .m file here! In a real-world example, you may have real model classes that need to be shown on a map as an annotation, and the MKAnnotation framework lets you add this capability to any class of your own without messing up any existing class hierarchies.

Select BIDViewController.m and get started by importing the header for the new class:

#import "BIDPlace.h"

Now find the viewDidLoad method and add this line to the end of it:

self.mapView.showsUserLocation = YES;

That does just what you probably imagine: it saves us the hassle of manually moving a marker around as the user moves by automatically drawing one for us.

Now let’s revisit the locationManager:didUpdateLocations: method. We’ve already got some code in there that notices the first valid location data we receive and establishes our start point. We’re also going to allocate a new instance of our BIDPlace class. We set its properties, giving it a location. We also add a title and subtitle that we want to appear when a marker for this location is displayed. Finally, we pass this object off to the map view.

We also create a new MKCoordinateRegion, a special struct included in Map Kit that lets us tell the view which section of the map we want it to display. MKCoordinateRegion uses our new location’s coordinates and a pair of distances in meters (100, 100) that specify how wide and tall the displayed map portion should be. We pass this off to the map view as well, telling it to animate the change. All of this is done by adding the bold lines shown here:

if (self.previousPoint == nil) {
    self.totalMovementDistance = 0;
 
    BIDPlace *start = [[BIDPlace alloc] init];
    start.coordinate = newLocation.coordinate;
    start.title = @"Start Point";
    start.subtitle = @"This is where we started!";
    
    [self.mapView addAnnotation:start];
    MKCoordinateRegion region;
    region = MKCoordinateRegionMakeWithDistance(newLocation.coordinate,
                                                100, 100);
    [self.mapView setRegion:region animated:YES];
} else {
    self.totalMovementDistance += [newLocation
                              distanceFromLocation:self.previousPoint];
}
self.previousPoint = newLocation;

So now we’ve told the map view that we have an annotation (i.e., a visible place-marker) that we want the user to see. But how should it be displayed? Well, the map view figures out what sort of view to display for each annotation by asking its delegate. In a more complex app, that would work for us. But in this example we haven’t made ourselves a delegate, simply because it’s not necessary for our simple use case. Unlike UITableView, which requires its data source to supply cells for display, MKMapView has a different strategy: if it’s not provided with annotation views by a delegate, it simply displays a default sort of view represented by a red “pin” on the map that reveals some more info when touched. Neat!

Now build and run your app, and you’ll see the map view load. As soon as it gets valid position data, you’ll see it scroll to the right location, drop a pin at your starting point, and mark your current location with a glowing blue dot (see Figure 19-5). Not bad for a few dozen lines of code!

9781430260226_Fig19-05.jpg

Figure 19-5. The red pin marks our starting location, and the blue dot shows how far we’ve gotten

Wherever You Go, There You Are

You’ve now seen pretty much all there is to Core Location. You’ve also seen the basic operation of Map Kit, as well. Although the underlying technologies are quite complex, Apple has provided simple interfaces that hide most of the complexity, making it quite easy to add location-related and mapping features to your applications so you can tell where the users are, notice when they move, and mark their location (and any other locations) on a map.

And speaking of moving, when you’re ready proceed directly to the next chapter, so we can play with the iPhone’s built-in accelerometer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.204.216