Figure 17-1. Pigeon design
The app has a map and three buttons. The middle button remembers your current location and drops a pin
into the map to mark it. When you move away from that location, the map displays where you are, the
saved location,and an arrow showing the direction back. A trash button forgets the saved location, and an
info button lets the user change map display options. Let’s get started.
Start by creating the project and laying out the interface. In Xcode, create a new project as follows:
1. Use the Single View Application template
2. Name the project Pigeon
3. Use a class prefix of HP
4. Set devices to Universal
Select the Main_iPhone.storyboard file. (You can choose to develop the iPhone interface, the iPad interface, or both; the steps are the same.) Add a toolbar to the bottom of the interface. Add and configure toolbar button itemsas follows (from left to right):
1. Bar Button Item: set its identifier to Trash
2. Flexible Space Bar Button Item
3. Bar Button Item: set its title to Remember Location
4. Flexible Space Bar Button Item
5. Button (not a Bar Button Item): set its type to Info Light
From the object library, add a Map View object to fill the rest of the interface. Set the following attributes for the map view object:
Check Shows User Location
Check Allows Zooming
Uncheck Allows Scrolling
Uncheck 3D Perspective
Complete the layout by choosing the Add Missing Constraints to View Controller command,
either from the Editor ➤ Resolve Auto Layout Issues submenu or the Resolve Auto Layout Issues
button at the bottom of the editor pane. The finished interface should look like Figure 17-2.
Figure 17-2. Pigeon interface
You’ll need to wire these views up to your controller, so do that next. Switch to the assistant editor and make sure HPViewController.h appears in the right-hand pane. Immediately after the
#import <UIKit/UIKit.h> statement, add an #import statement to pull in the Map Kit declarations:
#import <MapKit/MapKit.h>
Add the following connections to the @interface section:
@property (weak,nonatomic) IBOutlet MKMapView *mapView;
- (IBAction)dropPin:(id)sender;
- (IBAction)clearPin:(id)sender;
Connect the mapView outlet to the map view object. Connect the actions of the left and center toolbar
buttons to the -clearPin: and -dropPin: actions, respectively. Now you’re ready to begin coding the actions.
Collecting Location Data
Getting location data follows the same pattern you used to get gyroscope and magnetometer data in Chapter 16, with only minor modifications. The basic steps are:
n Create an instance of CLLocationManager.
n If precise (GPS) location information is a requirement for your app, add the gps
value to the app’s Required Device Capabilities property.
n Check to see if location services are available using the
+locationServicesAvailable or +authorizationStatus methods.
n Designate an object to be the CLLocationManager’s delegate. Adopt the
CLLocationManagerDelegate protocol and make that object the delegate.
n Send -startUpdatingLocation to begin collecting location data.
n The delegate object will receive messages whenever the device’s location changes.
n Send -stopUpdatingLocation when your app no longer needs location data.
The only significant difference between using CLLocationManager and CMMotionManager is that you cancreate multiple CLLocationManager objects and data is delivered to its delegate object
(rather than requiring your appto pull the data or pushing it to an operation queue).
Another difference is that location data may not be available, even on devices that have GPS hardware. Thereare a lot of reasons this might be true. The user may have location services turned off. They may be somewhere they can’t receive satellite signals. The device may be in “airplane mode,” which doesn’t permit the GPS receivers to be energized. Or your app may specifically have been denied access to location information. It
doesn’t reallymatter why. You need to check for the availability of location data and deal with the possibility
that you can’t get it.
Finally, there are a number of different methods of getting location data depending on the precision of the data and how quickly it’s delivered. Knowing that the user moved 20 feet to the left is a different problem from
knowingwhen they’ve arrived at work. I’ll describe the different kinds of location monitoring towards the end
of the chapter.
Pigeon needs precise location information that only GPS hardware can deliver. Select the Pigeon project
in the project navigator, select the Pigeon target (pop-up menu in upper-left corner, as shown in
Figure 17- 3), switchto the Info tab, and locate the Required device capabilities in the Custom iOS Target
Properties group. Click the + button and add a gps requirement, as shown in Figure 17-3.
Figure 17-3. Adding gps device requirement
You’re now probably thinking that I’m going to have you add some code to:
Make the HPViewController adopt the CLLocationManagerDelegate protocol Implement the -locationManager:didUpdateLocations: delegate method to process
the location updates
Create an instance of CLLocationManager
Make the HPViewController the location manager’s delegate Send -startUpdatingLocation to begin collecting location data
But you’re not going to do any of that.
Now I’m sure you wondering why not, so let me explain. Pigeon uses both location services and the Map Kit.
The Map Kit includes the MKMapView object, which displays maps. Among its many talents, it has the ability tomonitor the device’s current location and display that on the map. It will even notify its delegate when the user’s location changes.
For this particular app, MKMapView is already doing all of the work for you. When you ask it to display the
user’s location, it creates its own instance of CLLocationManager and begins monitoring location changes, updatingthe map and its delegate. The end result is that MKMapView has all of the information that Pigeon
needs to work.
This is a good thing. All of that CLLocationManager code would look so much like the code you wrote in Chapter 16 that it would make this app a little boring, and I certainly don’t want you to get bored. Or maybe you haven’tread Chapter 16 yet, in which case you have something to look forward to.
Regardless, all you need to do is setup MKMapView correctly. Let’s do that now.
Using a Map View
Your map view object has already been added to the interface and connected to the mapView outlet. You’ve also used the attributes inspector to configure the map view so it shows (and tracks) the user’s location and youdisallowed user scrolling. There’s one more setting that you need to make, and you can’t set it from the attributes inspector.
Select the HPViewController.m file and location the -viewDidLoad method. At the end, add this statement:
[_mapView setUserTrackingMode:MKUserTrackingModeFollow];
This sets the map’s tracking mode to “follow the user.” There are three tracking modes—which I’m sure you’ve see in places like Apple’s Maps app—listed in Table 17-1.
Table 17-1. User tracking modes
Tracking
Mode Description
MKUserTrackingModeNone The map does not follow the user’s location.
MKUserTrackingModeFollow The map is centered at the user’s current location and it moves when the user moves.
MKUserTrackingModeFollowWithHeading The map tracks the user’s current location and the orientation of
the map is rotated to indicate the user’s direction of travel.
The code you added to viewDidLoad: sets the tracking mode to follow the user. The combination of the
showsUserLocation property and the tracking mode force the map view to begin gathering location data, which is whatyou want.
If you’ve played with the Maps app, you also know that you can “break” the tracking mode by manually panning the map. You disabled panning for the map view, but there are still circumstances where the tracking
mode willrevert to MKUserTrackingModeNone. To address that you need to add code to catch the situation
where the tracking mode changes and “correct” it, if necessary.
That information is provided to the map view’s delegate. Wouldn’t it be great if your MPViewController object
were the delegate for the map view? I thought so too.
Switch to HPViewController.h and adopt the MKMapViewDelegate protocol (new code in bold):
@interface HPViewController : UIViewController <MKMapViewDelegate>
Return to HPViewController.m and add this map view delegate method to the @implementation:
- (void) mapView:(MKMapView *)mapView didChangeUserTrackingMode:(MKUserTrackingMode)mode
animated:(BOOL)animated
{
if (mode==MKUserTrackingModeNone)
[mapView setUserTrackingMode:MKUserTrackingModeFollow];
}
This message is received whenever the tracking mode for the map changes. It simply sees if the mode has changed to “none” and sets it back to tracking the user.
Of course, this message is only received if your HPViewController object is the delegate object for the map
view. Select the Main_iPhone.storyboard (or _iPad) file. Select the map view object and use the connections
inspectorto connect the map view’s delegate outlet to the view controller, as shown in Figure 17-4.
Figure 17-4. Connecting the map view’s delegate outlet
You’ve done everything you need to see the map view in action, so fire it up. Run your app in the simulator, or on a provisioned device. You may see build warnings that you haven’t implemented the-dropPin: and -clearPin: methods; ignore them, for now.
Having problems? Maybe your app isn’t ready to run. If your app crashes with a blank screen and a nasty message in the console pane, it’s because your app isn’t linked to the MapKit framework. In most cases, Xcode willautomatically link your app to the frameworks it needs, but in some situations you need to do that explicitly. Don’t worry, this is really easy to fix.
Select the Pigeon project, make sure the Pigeon target is selected, and switch to the General tab.
Locate the Linked Frameworks and Libraries section, as shown on the left in Figure 17-5.
Click on the + button and choose a new library or framework to link against, as shown on the right in
Figure 17-5.
Figure 17-5. Adding a framework to the project
You’re not really adding anything to your project. Every iOS symbol your app uses must, eventually, be translated into the memory address of that variable or class when it runs. This process is called dynamic linking, and it’spart of the mechanism that launches your app. The libraries and frameworks you “link to”during development are little more than a list of symbol names that iOS provides, along with instructions on how your app can connect tothem when it runs. Adding the MapKit framework gives your app access to all of the classes, variables, and constants in the Map Kit service.
With that annoying detail out of the way, run your app again. This time, you should see something like what’s shown in Figure 17-6.
Figure 17-6. Testing map view
The first time your app runs, iOS will ask the user if it’s OK for your app to collect location data.
Tap OK, or this is going to be a really short test. Once it’s granted permission, the map locates your device and centers the map at your location (second screen shot in Figure 17-6).
The iOS simulator will emulate location data, allowing you to test location-aware apps. In the Debug menu you’ll find a number of choices in the Location submenu (third screen shot in Figure 17-6).
Choose the CustomLocation… item to enter the longitude and latitude of your simulated location. There are also a few pre-programmed locations, such as the Apple item, also shown in Figure 17-6.
Some of the items play back a recorded trip. Currently the choices are City Bicycle Ride, City Run, and Freeway Drive. Selecting one starts a series of location changes, which the map will track, as though the device was on a bicycle, accompanying a jogger, or in a car. Go ahead and try one; you know youwant to.
The map can also be zoomed in and out by pinching or double tapping, as shown on the right in Figure 17-6. You can’t scroll the map, as you disabled that option in Interface Builder.
While the freeway drive is playing back, add the code to mark your location on the map.
Decorating Your Map
There are three ways of adding visual elements to a map: annotations, overlays, and subviews.
An annotation identifies a single point on the map. It can appear anyway you like, but iOS provides classes that mark the location with a recognizable “map pin” image. Annotations can optionally display a callout that consistsof a title, subtitle, and accessory views. The callout appears above the pin when selected (tapped).
An overlay identifies a path or region on the map. You won’t use overlays in Pigeon, but that’s how lines
(like driving directions) are drawn and arbitrary areas (like a city park) are highlighted.
A subview is like any other subview. MKMapView is a subclass of UIView, and you are free to add custom UIView objects to it. Use subviews to add additional controls or indicators to the map.
Annotation and overlay are attached to the map. They are described using map coordinates
(which I’ll talk about later) and they move when the map moves. Subviews are positioned in the local
graphics coordinate systemof the MKMapView object. They do not move with the map.
Your Pigeon app will create an annotation—that is, “put a pin”—at the user’s current location when they tap the “remember location” button. The trash button will discard the pin. That sounds like a couple of action methods tome.
Adding an Annotation
When the user taps the “remember location” button, you’ll capture their current location and add an annotation to the map. I thought it would be nice if the user could choose a label for the location, to make it easier to rememberwhatthey’re trying to remember. To accomplish all that, you’ll need an instance variable to store the annotation
object and your HPViewController will need to be an alert view delegate. Add both of those to the private @interfacesection inHPViewController.m (new code in bold):
@interface HPViewController () <UIAlertViewDelegate>
{
MKPointAnnotation *savedAnnotation;
}
@end
The next step is to write the -dropPin: method. It begins by presenting an alert view configured so the user can type in a label. Add this to the @implementation section:
- (IBAction)dropPin:(id)sender
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"What's here?"
message:@"Type a label for this location." delegate:self
cancelButtonTitle:@"Cancel" otherButtonTitles:@"Remember", nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput; alert.delegate = self;
[alert show];
}
This method creates an alert and sets its style to UIAlertViewStylePlainTextInput. This presents an alert
view with a regular text field the user can type something into. The functional part of the dropPin: action
occurs when the user finishes typing in their label and taps the “Remember” button. Add that after the -dropPin: method:
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
CLLocation *location = _mapView.userLocation.location; if (location==nil)
return;
NSString *name = [[alertView textFieldAtIndex:0] text]; name = [name stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (name.length==0)
name = @"Over Here!";
[self clearPin:self];
savedAnnotation = [MKPointAnnotation new]; savedAnnotation.title = name; savedAnnotation.coordinate = location.coordinate; [_mapView addAnnotation:savedAnnotation];
[_mapView selectAnnotation:savedAnnotation animated:YES];
}
The first step is to get the user’s current location. Remember that the map view has been tracking their location since the app was started, so it should have a pretty good idea of where they are by now.
You must, however, consider the possibility that the map view doesn’t know (location==nil). The user may have disabled location services, is running in “airplane mode,” or is spelunking. Regardless, if there’s no
location there’s nothing to do.
The next bit of code cleans up what the user typed. It strips off any leading or trailing whitespace
characters (a.k.a. spaces) and supplies a readable label if the user neglected to.
Now the method gets down to work. It clears any existing pin; Pigeon remembers only one location at a timeIt creates a new annotation object, sets its title and coordinates, adds the annotation to the map, and selects
it. Since you haven’t done anything special (yet), the map view will use the standard red map pin annotation view to indicate the location on the map. Programmatically selecting the new annotation causes its callout
it. Since you haven’t done anything special (yet), the map view will use the standard red map pin annotation view to indicate the location on the map. Programmatically selecting the new annotation causes its callout
to pop-up, as if the user tapped the pin.
While you’re still in HPViewController.m, toss in the -clearPin: method, which doesn’t need much explanation:
- (IBAction)clearPin:(id)sender
{
if (savedAnnotation!=nil)
{
[_mapView removeAnnotation:savedAnnotation]; savedAnnotation = nil;
}
}
Run the app and give it a try. Tap the “remember location” button, enter a label, and a pin appears at your current location, as shown in Figure 17-7.
Figure 17-7. Testing the annotation
Map Coordinates
The coordinates of the annotation object were set to the coordinates of the user’s location (provided by the map view). But what are these “coordinates?” Map Kit uses three coordinate systems, listed in Table 17-2.
Table 17-2. Map coordinate systems
Coordinate
System Description
Latitude and Longitude The latitude, longitude, and sometimes altitude, of a position on the planet.
These are called map coordinates.
Mercator The position (x,y) on the Mercator map of the planet. A Mercator map is a cylindrical projection of the planet’s surface onto a flat map. The Mercator map is what you see in the map view. Positions on the Mercator map arecalled map points.
Graphics The graphics coordinates in the interface, used by UIView.
These are referred to simply as points.
Map coordinates (longitude and latitude) are the principle values used to identify locations on the map,
stored in a CLLocationCoordinate2D structure. They are not XY coordinates, so calculating distance and heading betweentwo coordinates is a non trivial exercise that’s best left to location services and Map Kit. Annotations are positioned at map coordinates.
Map points are XY positions in the Mercator map projection. Being XY coordinates on a flat plane, calculating angles and distances is much simpler. Map points are used when drawing overlays. This simplifies drawing andreduces the math involved.
Map points are eventually translated into graphic coordinates, so they can appear somewhere on the screen. There are methods to translate map coordinates into graphic coordinates. Additional methods translate the otherway. You’ll use these, along with some utility methods to calculate the distance between
coordinates, later in this project.
Adding a Little Bounce
Your map pin appears on the map, and it moves around with the map. You can tap on it to show, or hide, its callout. Which is pretty impressive, considering you only needed a few lines of code to create it. We do, however, loveanimation and I’m sure you’ve seen map pins that “drop” into place. Your pin doesn’t drop; it justappears. So how do you get your map pin to animate, or change its color, or customize it in any other way? The answer is touse a custom annotation view.
An annotation in a map is actually a pair of objects: an annotation object and an annotation view object. An annotation object associates information with a coordinate on the map—the data model. An annotation view object isresponsible for how that annotation looks—the view. If you want to customize how an annotation appears you must supply your own annotation view object.
You do this by implementing the -mapView:viewForAnnotation: delegate method. When the map view wants to display an annotation, it sends its delegate this message with the annotation object. The method’s
job is to returnan annotation view object that represents that annotation. If you don’t implement this method,or decide to return nil for an annotation, the map view uses its default annotation view, which is a plain map pin.
Add this method to HPViewController.m:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if (annotation==self.mapView.userLocation) return nil;
NSString *pinID = @"Save";
MKPinAnnotationView *view = (MKPinAnnotationView*)
[self.mapView dequeueReusableAnnotationViewWithIdentifier:pinID]; if (view==nil)
{
view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pinID];
view.canShowCallout = YES; view.animatesDrop = YES;
}
return view;
}
The first statement compares the annotation to the map view’s user annotation object. The user annotation
object, like any other annotation object, represents the user’s position in the map. The map view automaticallyadded itwhen you asked it to display the user’s location. This automatic annotation is available via the map
view’s userLocation property. If you return nil for this annotation, the map view uses its default user annotation
view—the pulsingblue dot that we’re all familiar with. If you want to represent the user’s location some other way, this is where you’d provide that view.
The rest of the code works exactly like the table view cell code from Chapter 4.The map view maintains a cache of reusable MKAnnotationView objects that you recycle using an identifier. Your map only uses one kind ofannotation view: a standard map pin view provided by the MKPinAnnotationView class. The pin is configured to display callouts and animate itself (“drop in”) when added to the map
Run the app again. Now when you save the location, the pin animates its insertion into the map, which
is a lot more appealing.
Your -mapView:viewForAnnotation: delegate method can return a customized version of a built-in annotation view class, as you’ve done here. MKPinAnnotationView can display pins of different colors, can allow or disallow callouts,have custom accessory views in its callout, and so on. Alternatively, you
can subclass MKAnnotationView and create your own annotation view, with whatever custom graphics and animations you want. You could represent theuser’s location as a waddling duck. Let your imagination run wild.
Pointing the Way
The other technique for augmenting your map view is to add your own subviews. These can impart additional information (direction of travel, elapsed trip time, and so on) or you can add control objects (switches, musicplayback buttons, and the like).
Pigeon is going to add an image view that displays an arrow. The arrow will point from the user’s current location back to the location they marked on the map. Here’s how it will work:
The map view delegate receives a message when the user’s location changes.
This method will calculate the coordinate (on the screen) of the user’s location and the remembered location, and the real-world distance between them.
If the distance is more than 50 meters away, position the image view (the arrow) over the user’s screen location and rotated so it points towards the remembered location.
Otherwise, hide the arrow.
To make this happen, you’re going to need an arrow image, an instance variable for the image view, a method that hides the arrow, and a method to display the arrow and point it in the correct direction. Start by adding thearrow.png resource file, which you’ll find in the Learn iOS Development Projects ➤ Ch 17 ➤ Pigeon (Resources) folder, to your Images.xcassets image catalog. While you’re adding images, there’s a set of app icons in the Pigeon (Icons) folder you can drop into the AppIcons group.
In the HPViewController.m file, locate the private @interface section. Define the threshold distance constant, add an instance variable, and declare a couple of methods, as follows (new code in bold):
#define kArrowDisplayDistanceMin 50.0
@interface HPViewController () <UIAlertViewDelegate>
{
MKPointAnnotation *savedAnnotation;
UIImageView *arrowView;
}
- (void)hideReturnArrow;
- (void)showReturnArrowAtPoint:(CGPoint)userPoint towards:(CGPoint)returnPoint;
@end
The next step is to catch when the user’s location changes. When the user moves, the distance between the user and the remembered location changes, the map moves, and it alters the angle between the two. YourHPViewController is already the map view’s delegate. All you have to do is implement this method:
- (void) mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
if (savedAnnotation!=nil)
{
CLLocationCoordinate2D coord = savedAnnotation.coordinate;
CLLocation *toLoc = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude];
CLLocationDistance distance =
[userLocation.location distanceFromLocation:toLoc]; if (distance>=kArrowDisplayDistanceMin)
{
CGPoint userPoint = [mapView convertCoordinate:userLocation.coordinate toPointToView:self.mapView];
CGPoint savePoint = [mapView convertCoordinate:coord toPointToView:self.mapView];
[self showReturnArrowAtPoint:userPoint towards:savePoint]; return;
}
}
[self hideReturnArrow];
}
The arrow is only displayed when there’s a saved location and the real-world distance between it and the user is more than 50 meters. The first two conditions test those prerequisites. The
-distanceFromLocation: method is particularly handy, as it handles the math involved in determining the distance between two map coordinates.
To position the arrow, you need to know that position on the screen and the position of the remembered location. The -convertCoordinate:toPointToView: method performs that conversion. You pass the method the mapcoordinate and it returns the view coordinate of that point on the
map. Those coordinates are passed to your -showReturnArrowAtPoint:towards: method. In all other cases, the arrow view is hidden.
The last step is to implement the -hideReturnArrow and -showReturnArrowAtPoint:towards: methods:
- (void)hideReturnArrow
{
arrowView.hidden = YES;
}
- (void)showReturnArrowAtPoint:(CGPoint)userPoint towards:(CGPoint)returnPoint
{
if (arrowView==nil)
{
UIImage *arrowImage = [UIImage imageNamed:@"arrow"]; arrowView = [[UIImageView alloc] initWithImage:arrowImage]; arrowView.opaque = NO;
arrowView.alpha = 0.6;
[self.mapView addSubview:arrowView]; arrowView.hidden = YES;
}
CGFloat angle = atan2f(returnPoint.x-userPoint.x,userPoint.y-returnPoint.y); CGAffineTransform rotation = CGAffineTransformMakeRotation(angle);
void (^updateArrow)(void) = ^{ arrowView.center = userPoint; arrowView.transform = rotation;
};
if (arrowView.hidden)
{ updateArrow(); arrowView.hidden = NO;
} else
{
[UIView animateWithDuration:0.5 animations:updateArrow];
}
}
What the -hideReturnArrow method does should be obvious.
The -showReturnArrowAtPoint:towards: method begins by lazily creating the image view object, if this is the first time it’s being displayed. The next couple of statements calculates the angle between the user’s point and theremembered point and creates a transform to rotate the arrow so it points from one to the other.
The rest of the code gets a little fancy—but in a good way. If the arrow was previously hidden, you want it to appear immediately, at its correct position, with no animation. If it’s currently being displayed, you want it to animate smoothly to its new position. The solution here captures the code that positions and rotates the arrow view in a code block variable (updateArrow). The following if statement either executes the code block immediately (noanimation) and shows the view, or it passes the code block to UIView for animation. See, that wasn’t too hard.
Run the app and test it out. Tap the “remember location” button to drop a pin in the map and then use the iOS simulator’s debug menu to move the simulated location. If you’re using a real device, then just move 50 meters in any direction, as shown in Figure 17-8.
Figure 17-8. Testing the direction arrow
Are you still wondering what the info button in the toolbar is for?
I saved that for the exercise at the end of the chapter. Before you get to that, let’s take a brief tour of some location services and map features you haven’texplored yet.
Location Monitoring
Pigeon is the kind of app that uses immediate, precise (as possible), and continuous monitoring
of the user’s location. Because of this, it requires an iOS device with GPS capabilities and gathers location data continuously. This isn’t true for all apps. Many apps don’t need precise location information, continuousmonitoring, or to be immediately notified of movement.
For apps with less demanding location requirements, the Core Location framework offers a variety
of information and delivery methods. Each method involves a different amount of hardware and CPU involvement, which means that each will impact the battery life and performance of the iOS device in varying ways.
As a rule, you want to gather the least amount of location information your app needs to function. Let’s say you’re writing a travel app that needs to know when the user has left one city and arrived at the next.
Do not fire upthe GPS hardware (the way Pigeon does) and start monitoring their every movement. Why?
Because your app will never get the notification that they’ve arrived in their destination city, because the
user’s battery will have been completely drained! And the first thing the user is going to do, afterrecharging,
is to delete your app. Take a look at some other ways of getting location information that don’t require as
much juice.
Approximate Location and Non-GPS Devices
Location information is also available on iOS devices that don’t have GPS hardware. These devices use location information that they gather from Wi-Fi base stations, cell phone towers, and other sources. The accuracy can becrude—sometimes kilometers, instead of meters—but it’s enough to place the user in a town. This is more than enough information to suggest restaurants or movies that are playing in their vicinity. So even if you left out the“gps” hardware requirement for your app, you can still request location information and you might get it.
Consult the horizontalAccuracy property of the CLLocation object for the uncertainty (in meters) ofthe location’s
reported position. If that value is large, then the device probably isn’t using GPS or it’s in a situation where GPS is not accurate.
If your app only needs approximate location information, gather your location data by sending
CLLocationManager the -startMonitoringSignificantLocationChanges message instead of the
-startUpdatingLocation message. This method only gets a rough estimate of the user’s location
and only notifies your app when that location changes in a big way, saving a great deal of processing power and battery life.
Monitoring Regions
Getting back to that travel app, some iOS devices are capable of monitoring significant changes in location, even when the device is idle. This is accomplished using region monitoring. Region monitoring lets you define an areaon the map and be notified when the user enters or exits that region. This is an extremely efficient (low-power) method of determining when the user has moved.
You could, for example, create two region (CLRegion) objects: one around the city the user is in and a second encompassing the city they are traveling to next. You would send the location manager object a -startMonitoringForRegion: message for each one, up to 20. Then all your app has to
do is sit back and wait until the delegate object receives a -locationManager:didEnterRegion: or
-locationManager:DidExitRegion: message.
Use region monitoring to be notified when the user arrives at work or at their family reunion. To learn more about region monitoring, find the “Monitoring Shape-Based Regions” section of the Location Awareness Programming Guide that you’ll find in Xcode’s Documentation and API Reference
window. The Location Awareness Programming Guide also describes how to receive location data in the background—when your app isn’t the active app, something I haven’t discussed in this book.
Reducing Location Change Messages
Another way to stretch battery life is to reduce the amount of location information your app receives. I already talked about receiving only significant changes or monitoring regions, but there’s a middle ground between thatextreme and getting second-by-second updates on the user’s location.
The first method is to set the location manager’s distanceFilter and desiredAccuracy properties. The distanceFilter reduces the number of location updates your app receives. It waits until the device has moved by the set distancebefore updating your app again. The desiredAccuracy property tells iOS how much effort it should expend trying to determine the user’s exact location. Relaxing that property means the location hardware doesn’t have to workas hard.
Another hint you can provide is the activityType property. This tells the manager that your app is used for automotive navigation, as opposed to being a personal fitness app. The location manager will use this hint to optimize itsuse of hardware. An automobile navigation app might, for example, temporarily power down the GPS receivers if the user hasn’t moved for an extended period of time.
Movement and Heading
Your app might not be interested so much in where the user is, as what direction they’re going in and how fast. If heading is your game, consult the speed and course properties of the CLLocation object that you obtain from thelocation property of the CLLocationManager.
If all you want to know is the user’s direction, you can gather just that by sending the location manager the -startUpdatingHeading message (instead of -startUpdatingLocation). The user’s heading can be determinedsomewhat more efficiently than their exact location.
To learn more about direction information, read the “Getting Direction-Related Events” chapter of the
Location Awareness Programming Guide.
Geocoding
What if your app is interested in places on the map? It might want to know where a business is located. Or maybe it has a map coordinate and wants to know what’s there.
The process of converting information about locations (business name, address, city, zip code) into map coordinates, and vice versa, is called geocoding. Geocoding is a network service, provided by Apple, that will convert a dictionary of place information (say, an address)into a longitude and latitude,
and back again, as best as it can. Turning place information into a map coordinate is called forward
geocoding.Turning a map coordinate into a description of what’s there is called reverse geocoding.
Geocoding is performed through the CLGeocoder object. CLGeocoder will turn either a dictionary
of descriptive information or a map coordinate into a CLPlacemark object. A placemark object is a combination of a map coordinate and a description of what’s at that coordinate. This information will include what country, region, and city the coordinate is in, a street address, and a postal code
(if appropriate), even whether it’s a body of water.
Getting Directions
Another resource your app has at its disposal is the Maps app. Yes, the standard Maps app that comes
with iOS. There are methods that let your app launch the Maps app to assist your user.
This is a simple way of providing maps, locations, directions, and navigation services to your user without adding any of that to your app.
You use the Maps app via the MKMapItem object. You create one or more MKMapItem objects either from the current location (+mapItemForCurrentLocation) or from a geocoded placemark object
(-initWithPlacemark:).
Once created, send the map item object an -openInMapsWithLaunchOptions: message (for one map item) or pass an array of map items to +openMapItems:launchOptions:. The launch options are a dictionary that can optionallytell the Maps app what region of the
globe to display, a region on the map to highlight, whether you want it provide driving or walking directions to a given location, what mode to use (map, satellite, hybrid), whether to displaytraffic information, and so on.
Code examples using MKMapItem are shown in the “Providing Directions” chapter of the Location
Awareness Programming Guide.
Summary
You’ve traveled far in your journey towards mastering iOS app development. You’ve passed many milestones, and learning to use location services is a big one. You now know how to get the user’s location, for a variety ofpurposes, and display that on a map.
Speaking of which, you also learned a lot about maps. You now know how to present a map in your app, annotate it with points of interest, and customize how those annotations look. You learned how to track and display theuser’s location on the map and add your own view controls to the interface.
But you know what? Pigeon still has the same problem that MyStuff has. What good is an app that’s supposed to remember stuff, if it forgets everything when you quit the app? There should be some way of storing its datasomewhere, so when you come back to the app it hasn’t lost everything. Not surprisingly, there’s a bunch of technologies for saving data, and the next two chapters are devoted to just that.








No comments:
Post a Comment