Wednesday, 7 May 2014

Creating Pigeon [Where Are You?]

The app for this chapter is called Pigeon. Its a utility that lets you remember your current location on a map. Later it will show you where you are and where the marked location is, so you can fly back to it. The design for Pigeonis shown in Figure 17-1.


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. Lets 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.       Ba 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 apps 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 CLLocationManagers 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 devices 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 cant receive satellite signals. The device may be in “airplane mode,” which doesnt permit the GPS receivers to be energized. Or your app may specifically have been denied access to location information. It
doesnt reallymatter why. You need to check for the availability of location data and deal with the possibility
that you cant get it.

Finally, there are a number of different methods of getting location data depending on the precision of the data and how quickly its 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 Pigeoproject
in the project navigator, select the Pigeon target (pop-up menu in upper-left corner, ashown in 
Figure 17- 3), switchto the Info tab, and locate the Required devic capabilities in the Custom iOS Target
Properties group. Click the + button and add a gps requirement, ashowin 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 managers 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 devices current location and display that on the map. It will even notify its delegate when the users location changes.

For this particular app, MKMapView is already doing all of the work for you. When you ask it to display the
users 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 dont want you to get bored. Or maybe you haventread Chapter 16 yet, in which case you have something to look forward to.

Regardless, all you need to do is setup MKMapView correctly. Lets 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 users location and youdisallowed user scrolling. Theres one more setting that you need to make, and you cant 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 maps tracking mode to “follow the user.” There are three tracking modes—which I’m sure you’ve see in places like Apples Maps app—listed in Table 17-1.

Table 17-1. User tracking modes

Tracking Mode                                                       Description

MKUserTrackingModeNone                                  The map does not follow the users location.

MKUserTrackingModeFollow                                The map is centered at the users current location and it moves when the user moves.
MKUserTrackingModeFollowWithHeading           The map tracks the users current location and the orientation of
                                                                                   the map is rotated to indicate the users 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 views delegate. Wouldnt 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 views delegate outlet to the view controlleras shown in Figure 17-4.


Figure 17-4. Connecting the map views 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 havent implemented the-dropPin: and -clearPin: methods; ignore them, for now.

Having problems? Maybe your app isnt ready to run. If your app crashes with a blank screen and a nasty message in the console pane, its because your app isnt 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. Dont 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 Linke Framework an Libraries section, ashown on the left in Figure 17-5.
Click on the + button and choose new library or framework to link against, ashown 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 itspart 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 whats shown in Figure 17-6.


Figure 17-6. Testing map view

The first time your app runs, iOS will ask the user if its OK for your app to collect location data.
Tap OK, or this is going to be a really short test. Once its 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 cant 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 wont use overlays in Pigeon, but thats 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 users 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 aannotation 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 storthe annotation
object and your HPViewControllewilneed to be an alert viedelegate. Add both of those to the privat@interfacesection inHPViewController.(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  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 users 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 doesnt know (location==nil). The user may have disabled location services, is running in “airplane mode,” or is spelunking. Regardless, if theres no
location theres 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 havent 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 doesnt 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 users 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 cylindricaprojection of the planets surface onto a flat map. The Mercator map is what you sein the map view. Positionon 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 thats 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 otherwayYou’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 doesnt 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 methods
job is to returnan annotation view object that represents that annotation. If you dont 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 views user annotation object. The user annotation
object, like any other annotation object, represents the users position in the map. The map view automaticallyadded itwhen you asked it to display the users location. This automatic annotation is available via the map
views 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 users 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 identifierYour 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-iannotation view class, as you’ve donhere. 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 graphicand animations you want. You could represent theuserlocation 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 users current location back to the location they marked on the map. Heres how it will work:

The map view delegate receives a message when the users location changes.

This method will calculate the coordinate (on the screen) of the users 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 users 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, theres 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 users 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 views 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 theres 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 -hideReturnArroand -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 its being displayed. The next couple of statements calculates the angle between the users 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 its 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 wasnt 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 simulators 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, lets take a brief tour of some location services and map features you haventexplored yet.


Location Monitoring

Pigeon is the kind of app that uses immediate, precise (as possible), and continuous monitoring
of the users location. Because of this, it requires an iOS device with GPS capabilities and gathers location data continuously. This isnt true for all apps. Many apps dont 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. Lets 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
users 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 dont require as
much juice.

Approximate Location and Non-GPS Devices

Location information is also available on iOS devices that dont 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 its enough to place the user in a town. This is more than enough information to suggest restaurants or movies that are playing in their vicinitySo 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 locations
reported position. If that value is large, then the device probably isnt using GPS or its 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 users 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 Xcodes Documentation and API Reference
window. The Location Awareness Programming Guide also describes how to receive location data in the background—when your app isnt the active app, something I havent 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 theres a middle ground between thatextreme and getting second-by-second updates on the users location.

The first method is to set the location managerdistanceFilter 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 users exact location. Relaxing that property means the location hardware doesnt 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 hasnt 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 users direction, you can gather just that by sending the location manager the -startUpdatingHeading message (instead of -startUpdatingLocation). The users 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 whats 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 whats 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 whats 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 its 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 users 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 theusers 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 thats 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 hasnt lost everything. Not surprisingly, theres a bunch of technologies for saving data, and the next two chapters are devoted to just that.

No comments:

Post a Comment