Tải bản đầy đủ (.pdf) (89 trang)

iOS 5 Programming Cookbook phần 6 doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.17 MB, 89 trang )

CHAPTER 6
Core Location and Maps
6.0 Introduction
The Core Location and Map Kit frameworks can be used to create location-aware and
map-based applications. The Core Location framework uses the internal device’s hard-
ware to determine the current location of the device. The Map Kit framework enables
your application to display maps to your users, put custom annotations on the maps,
and so on. The availability of location services depends on the availability of hardware
on the device; if the hardware is there, it must be enabled and switched on for the Map
Kit framework to work.
To use the Core Location and Map Kit frameworks, you need to first add them to your
project and make sure appropriate header files are imported. Follow these steps to add
these two frameworks to your project:
1. Click on your project icon in Xcode.
2. Select the target to which you want to add the frameworks to, as shown in Fig-
ure 6-1.
3. Now select the Build Phases tab on the top (Figure 6-1).
4. Expand the Link Binary With Libraries box (Figure 6-1) and press the + button.
429
Figure 6-1. Selecting the target to which we want to add the frameworks
5. In the dialog, you will see the list of all available frameworks and static libraries.
Find and select both the CoreLocation.framework and the MapKit.framework and
then press Add, as shown in Figure 6-2.
430 | Chapter 6: Core Location and Maps
Figure 6-2. Adding the CoreLocation and the MapKit frameworks to a project
After adding these two frameworks, you will need to add two header files to your .m
or .h file (in your .h file if you are referring to any entity that is included in either of the
two aforementioned frameworks):
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
6.1 Creating a Map View


Problem
You want to instantiate and display a map on a view.
Solution
Create an instance of the MKMapView class and add it to a view or assign it as a subview
of your view controller. Here is the sample .h file of a view controller that creates an
instance of MKMapView and displays it full-screen on its view:
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
6.1 Creating a Map View | 431
@interface Creating_a_Map_ViewViewController : UIViewController
@property (nonatomic, strong) MKMapView *myMapView;
@end
This is a simple root view controller with a variable of type MKMapView. Later in the
implementation of this view controller (.m file), we will initialize the map and set its
type to Satellite, like so:
#import "Creating_a_Map_ViewViewController.h"
@implementation Creating_a_Map_ViewViewController
@synthesize myMapView;
- (void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];
}
- (void)viewDidLoad{
[super viewDidLoad];

self.view.backgroundColor = [UIColor whiteColor];

self.myMapView = [[MKMapView alloc]
initWithFrame:self.view.bounds];
/* Set the map type to Satellite */
self.myMapView.mapType = MKMapTypeSatellite;


self.myMapView.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;

/* Add it to our view */
[self.view addSubview:self.myMapView];

}
- (void)viewDidUnload{
[super viewDidUnload];
self.myMapView = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation
:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
@end
432 | Chapter 6: Core Location and Maps
Discussion
Creating an instance of the MKMapView class is quite straightforward. We can simply
assign a frame to it using its constructor, and after the map is created, we will add it as
a subview of the view on the screen just so that we can see it.
MKMapView is a subclass of UIView, so you can manipulate any map view
the way you manipulate an instance of UIView.
If you haven’t already noticed, the MKMapView class has a property called mapType that
can be set to satellite, standard, or hybrid. In this example, we are using the satellite
map type (see Figure 6-3).
6.1 Creating a Map View | 433
Figure 6-3. A satellite map view

We can change the visual representation type of a map view using the mapType property
of an instance of MKMapView. Here are the different values we can use for this property:
MKMapTypeStandard
Use this map type to display a standard map (this is the default).
MKMapTypeSatellite
Use this map type to display a satellite image map (as depicted in Figure 6-3).
MKMapTypeHybrid
Use this map type to display a standard map overlaid on a satellite image map.
434 | Chapter 6: Core Location and Maps
See Also
XXX
6.2 Handling the Events of a Map View
Problem
You want to handle various events that a map view can send to its delegate.
Solution
Assign a delegate object, which conforms to the MKMapViewDelegate protocol, to the
delegate property of an instance of the MKMapView class:
/* Create a map as big as our view */
self.myMapView = [[MKMapView alloc]
initWithFrame:self.view.bounds];
/* Set the map type to Satellite */
self.myMapView.mapType = MKMapTypeSatellite;
self.myMapView.delegate = self;
self.myMapView.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
/* Add it to our view */
[self.view addSubview:self.myMapView];
This code can easily run in the viewDidLoad method of a view controller object that has
a property named MapView of type MKMapView:

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
@interface Handling_the_Events_of_a_Map_ViewViewController
: UIViewController <MKMapViewDelegate>
@property (nonatomic, strong) MKMapView *myMapView;
@end
Discussion
The delegate object of an instance of the MKMapView class must implement the methods
defined in the MKMapViewDelegate protocol in order to receive various messages from
the map view and, as we will see later, to be able to provide information to the map
view. Various methods are defined in the MKMapViewDelegate protocol, such as the
mapViewWillStartLoadingMap: method that will get called in the delegate object when-
6.2 Handling the Events of a Map View | 435
ever the map loading process starts. Bear in mind that a delegate for a map view is not
a required object, meaning that you can create map views without assigning delegates
to them; these views simply won’t respond to user manipulation.
Here is a list of some of the methods declared in the MKMapViewDelegate protocol and
what they are meant to report to the delegate object of an instance of MKMapView:
mapViewWillStartLoadingMap:
This method is called on the delegate object whenever the map view starts to load
the data that visually represents the map to the user.
mapView:viewForAnnotation:
This method is called on the delegate object whenever the map view is asking for
an instance of MKAnnotationView to visually represent an annotation on the map.
For more information about this, please refer to Recipe 6.4.
mapViewWillStartLocatingUser:
This method, as its name implies, gets called on the delegate object whenever the
map view starts to detect the user’s location. For information about finding a user’s
location, please refer to Recipe 6.3.
mapView:regionDidChangeAnimated:

This method gets called on the delegate object whenever the region displayed by
the map is changed.
6.3 Pinpointing the Location of a Device
Problem
You want to find the latitude and longitude of a device.
Solution
Use the CLLocationManager class:
if ([CLLocationManager locationServicesEnabled]){
self.myLocationManager = [[CLLocationManager alloc] init];
self.myLocationManager.delegate = self;

self.myLocationManager.purpose =
@"To provide functionality based on user's current location.";

[self.myLocationManager startUpdatingLocation];
} else {
/* Location services are not enabled.
Take appropriate action: for instance, prompt the
user to enable the location services */
NSLog(@"Location services are not enabled");
}
436 | Chapter 6: Core Location and Maps
In this code, myLocationManager is a property of type CLLocationManager. The current
class is also the delegate of the location manager in this sample code.
Discussion
The Core Location framework in the SDK provides functionality for programmers to
be able to detect the current spatial location of an iOS device. Because in iOS, the user
is allowed to disable location services using the Settings, before instantiating an object
of type CLLocationManager, it is best to first determine whether the location services are
enabled on the device.

The delegate object of an instance of CLLocationManager must conform
to the CLLocationManagerDelegate protocol.
This is how we will declare our location manager object in the .h file of a view controller
(the object creating an instance of CLLocationManager does not necessarily have to be a
view controller):
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
@interface Pinpointing_the_Location_of_a_DeviceViewController
: UIViewController <CLLocationManagerDelegate>
@property (nonatomic, strong) CLLocationManager *myLocationManager;
@end
The implementation of our view controller is as follows:
#import "Pinpointing_the_Location_of_a_DeviceViewController.h"
@implementation Pinpointing_the_Location_of_a_DeviceViewController
@synthesize myLocationManager;
- (void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation{

/* We received the new location */

NSLog(@"Latitude = %f", newLocation.coordinate.latitude);
NSLog(@"Longitude = %f", newLocation.coordinate.longitude);

}
6.3 Pinpointing the Location of a Device | 437
- (void)locationManager:(CLLocationManager *)manager

didFailWithError:(NSError *)error{

/* Failed to receive user's location */

}
- (void)viewDidLoad {
[super viewDidLoad];

if ([CLLocationManager locationServicesEnabled]){
self.myLocationManager = [[CLLocationManager alloc] init];
self.myLocationManager.delegate = self;

self.myLocationManager.purpose =
@"To provide functionality based on user's current location.";

[self.myLocationManager startUpdatingLocation];
} else {
/* Location services are not enabled.
Take appropriate action: for instance, prompt the
user to enable the location services */
NSLog(@"Location services are not enabled");
}

}
- (void) viewDidUnload{
[super viewDidUnload];
[self.myLocationManager stopUpdatingLocation];
self.myLocationManager = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation

:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
@end
The startUpdateLocation instance method of CLLocationManager reports the success
or failure of retrieving the user’s location to its delegate through the location
Manager:didUpdateToLocation:fromLocation: and locationManager:didFailWithError:
methods of its delegate object, in that order.
The locationServicesEnabled class method of CLLocationManager is
available in SDK 4.0 and later.
438 | Chapter 6: Core Location and Maps
The CLLocationManager class implements a property named purpose. This property al-
lows us to customize the message that is shown to the users of our application, asking
for their permission to allow location services for our application using Core Location
functionalities. A good practice is to use localized strings for the value of this property.
6.4 Displaying Pins on a Map View
Problem
You want to point out a specific location on a map to the user.
Solution
Use built-in map view annotations.
Follow these steps:
1. Create a new class and call it MyAnnotation.
2. Make sure this class conforms to the MKAnnotation protocol.
3. Define a property for this class of type CLLocationCoordinate2D and name it coor
dinate. Also make sure you set it as a readonly property since the coordinate prop-
erty is defined as readonly in the MKAnnotation protocol.
4. Optionally, define two properties of type NSString, namely title and subtitle,
which will be able to carry the title and the subtitle information for your
annotation view. Both of these properties are readonly as well.
5. Create an initializer method for your class that will accept a parameter of type

CLLocationCoordinate2D. In this method, assign the passed location parameter to
the property that we defined in step 3. Since this property is readonly, it cannot be
assigned by code outside the scope of this class. Therefore, the initializer of this
class acts as a bridge here and allows us to indirectly assign a value to this property.
We will do the same thing for the title and the subtitle properties.
6. Instantiate the MyAnnotation class and add it to your map using the add
Annotation: method of the MKMapView class.
Discussion
As explained in this recipe’s Solution, we must create an object that conforms to the
MKAnnotation protocol and later instantiate this object and pass it to the map to be
displayed. We will write the .h file of this object like so:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface MyAnnotation : NSObject <MKAnnotation>
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
6.4 Displaying Pins on a Map View | 439
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, copy, readonly) NSString *subtitle;
- (id) initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates
title:(NSString *)paramTitle
subTitle:(NSString *)paramSubTitle;
@end
The .m file of the MyAnnotation class sets up the class to display location information
as follows:
#import "MyAnnotation.h"
@implementation MyAnnotation
CLLocationCoordinate2D coordinate;
@synthesize coordinate, title, subtitle;
- (id) initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates
title:(NSString *)paramTitle

subTitle:(NSString *)paramSubTitle{

self = [super init];

if (self != nil){
coordinate = paramCoordinates;
title = paramTitle;
subtitle = paramSubTitle;
}

return(self);

}
@end
Later we will instantiate this class and add it to our map, for instance, in the .m file of
a view controller that creates and displays a map view:
#import "Displaying_Pins_on_a_Map_ViewViewController.h"
#import "MyAnnotation.h"
@implementation Displaying_Pins_on_a_Map_ViewViewController
@synthesize myMapView;
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidLoad {
[super viewDidLoad];
440 | Chapter 6: Core Location and Maps

/* Create a map as big as our view */

self.myMapView = [[MKMapView alloc]
initWithFrame:self.view.bounds];

self.myMapView.delegate = self;

/* Set the map type to Standard */
self.myMapView.mapType = MKMapTypeStandard;

self.myMapView.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;

/* Add it to our view */
[self.view addSubview:self.myMapView];

/* This is just a sample location */
CLLocationCoordinate2D location =
CLLocationCoordinate2DMake(50.82191692907181, -0.13811767101287842);

/* Create the annotation using the location */
MyAnnotation *annotation =
[[MyAnnotation alloc] initWithCoordinates:location
title:@"My Title"
subTitle:@"My Sub Title"];

/* And eventually add it to the map */
[self.myMapView addAnnotation:annotation];

}
- (void) viewDidUnload{

[super viewDidUnload];
self.myMapView = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation
:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
@end
Figure 6-4 depicts the output of the program when run in iPhone Simulator.
6.4 Displaying Pins on a Map View | 441
Figure 6-4. A built-in pin dropped on a map
See Also
XXX
442 | Chapter 6: Core Location and Maps
6.5 Displaying Pins with Different Colors on a Map View
Problem
The default color for pins dropped on a map view is red. You want to be able to display
pins in different colors in addition to the default red pin.
Solution
Return instances of MKPinAnnotationView to your map view through the mapView:view
ForAnnotation: delegate method.
Every annotation that is added to an instance of MKMapView has a corresponding view
that gets displayed on the map view. These views are called annotation views. An an-
notation view is an object of type MKAnnotationView, which is a subclass of UIView. If
the delegate object of a map view implements the mapView:viewForAnnotation: delegate
method, the delegate object will have to return instances of the MKAnnotationView class
to represent and, optionally, customize the annotation views to be displayed on a
map view.
Discussion
To set up our program so that we can customize the color (choosing from the default

SDK pin colors) of the annotation view that gets dropped on a map view, representing
an annotation, we must return an instance of the MKPinAnnotationView class instead of
an instance of MKAnnotationView in the mapView:viewForAnnotation: delegate method.
Bear in mind that the MKPinAnnotationView class is a subclass of the MKAnnotationView
class.
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation{

MKAnnotationView *result = nil;

if ([annotation isKindOfClass:[MyAnnotation class]] == NO){
return result;
}

if ([mapView isEqual:self.myMapView] == NO){
/* We want to process this event only for the Map View
that we have created previously */
return result;
}

/* First typecast the annotation for which the Map View has
fired this delegate message */
MyAnnotation *senderAnnotation = (MyAnnotation *)annotation;

/* Using the class method we have defined in our custom
annotation class, we will attempt to get a reusable
6.5 Displaying Pins with Different Colors on a Map View | 443
identifier for the pin we are about
to create */
NSString *pinReusableIdentifier =

[MyAnnotation
reusableIdentifierforPinColor:senderAnnotation.pinColor];

/* Using the identifier we retrieved above, we will
attempt to reuse a pin in the sender Map View */
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)
[mapView
dequeueReusableAnnotationViewWithIdentifier:pinReusableIdentifier];

if (annotationView == nil){
/* If we fail to reuse a pin, then we will create one */
annotationView = [[MKPinAnnotationView alloc]
initWithAnnotation:senderAnnotation
reuseIdentifier:pinReusableIdentifier];

/* Make sure we can see the callouts on top of
each pin in case we have assigned title and/or
subtitle to each pin */
[annotationView setCanShowCallout:YES];
}

/* Now make sure, whether we have reused a pin or not, that
the color of the pin matches the color of the annotation */
annotationView.pinColor = senderAnnotation.pinColor;

result = annotationView;

return result;
}
An annotation view must be reused by giving it an identifier (an NSString). By deter-

mining which type of pin you would like to display on a map view and setting a unique
identifier for each type of pin (e.g., blue pins can be treated as one type of pin
and red pins as another), you must reuse the proper type of pin using the
dequeueReusableAnnotationViewWithIdentifier: instance method of MKMapView as dem-
onstrated in the code.
We have set the mechanism of retrieving the unique identifiers of each pin in our custom
MyAnnotation class. Here is the .h file of the MyAnnotation class:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
/* These are the standard SDK pin colors. We are setting
unique identifiers per color for each pin so that later we
can reuse the pins that have already been created with the same
color */
#define REUSABLE_PIN_RED @"Red"
#define REUSABLE_PIN_GREEN @"Green"
#define REUSABLE_PIN_PURPLE @"Purple"
444 | Chapter 6: Core Location and Maps
@interface MyAnnotation : NSObject <MKAnnotation>
@property (nonatomic, unsafe_unretained, readonly)
CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@property (nonatomic, unsafe_unretained) MKPinAnnotationColor pinColor;
- (id) initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates
title:(NSString*)paramTitle
subTitle:(NSString*)paramSubTitle;
+ (NSString *) reusableIdentifierforPinColor
:(MKPinAnnotationColor)paramColor;
@end
Annotations are not the same as annotation views. An annotation is the location that

you want to show on a map and an annotation view is the view that represents
that annotation on the map. The MyAnnotation class is the annotation, not the anno-
tation view. When we create an annotation by instantiating the MyAnnotation class, we
can assign a color to it using the pinColor property that we have defined and imple-
mented. When the time comes for a map view to display an annotation, the map view
will call the mapView:viewForAnnotation: delegate method and ask its delegate for an
annotation view. The forAnnotation parameter of this method passes the annotation
that needs to be displayed. By getting a reference to the annotation, we can type-cast
the annotation to an instance of MyAnnotation, retrieve its pinColor property, and based
on that, create an instance of MKPinAnnotationView with the given pin color and return
it to the map view.
This is the .m file of MyAnnotation:
#import "MyAnnotation.h"
@implementation MyAnnotation
@synthesize coordinate;
@synthesize title;
@synthesize subtitle;
@synthesize pinColor;
+ (NSString *) reusableIdentifierforPinColor
:(MKPinAnnotationColor)paramColor{

NSString *result = nil;

switch (paramColor){
case MKPinAnnotationColorRed:{
result = REUSABLE_PIN_RED;
break;
6.5 Displaying Pins with Different Colors on a Map View | 445
}
case MKPinAnnotationColorGreen:{

result = REUSABLE_PIN_GREEN;
break;
}
case MKPinAnnotationColorPurple:{
result = REUSABLE_PIN_PURPLE;
break;
}
}

return result;
}
- (id) initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates
title:(NSString*)paramTitle
subTitle:(NSString*)paramSubTitle{

self = [super init];

if (self != nil){
coordinate = paramCoordinates;
title = paramTitle;
subtitle = paramSubTitle;
pinColor = MKPinAnnotationColorGreen;
}

return self;

}
@end
After implementing the MyAnnotation class, it’s time to use it in our application (in this
example, we will use it in a view controller). Here is the .h file of the view controller:

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
@interface Displaying_Pins_with_Different_Colors_on_a_Map_ViewViewController
: UIViewController <MKMapViewDelegate>
@property (nonatomic, strong) MKMapView *myMapView;
@end
The implementation is in the .m file like so:
#import "Displaying_Pins_with_Different_Colors_on_a_Map_ViewViewController.h"
#import "MyAnnotation.h"
@implementation
Displaying_Pins_with_Different_Colors_on_a_Map_ViewViewController
@synthesize myMapView;
446 | Chapter 6: Core Location and Maps
- (void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation{

MKAnnotationView *result = nil;

if ([annotation isKindOfClass:[MyAnnotation class]] == NO){
return result;
}

if ([mapView isEqual:self.myMapView] == NO){
/* We want to process this event only for the Map View
that we have created previously */
return result;
}


/* First typecast the annotation for which the Map View has
fired this delegate message */
MyAnnotation *senderAnnotation = (MyAnnotation *)annotation;

/* Using the class method we have defined in our custom
annotation class, we will attempt to get a reusable
identifier for the pin we are about
to create */
NSString *pinReusableIdentifier =
[MyAnnotation
reusableIdentifierforPinColor:senderAnnotation.pinColor];

/* Using the identifier we retrieved above, we will
attempt to reuse a pin in the sender Map View */
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)
[mapView
dequeueReusableAnnotationViewWithIdentifier:pinReusableIdentifier];

if (annotationView == nil){
/* If we fail to reuse a pin, then we will create one */
annotationView = [[MKPinAnnotationView alloc]
initWithAnnotation:senderAnnotation
reuseIdentifier:pinReusableIdentifier];

/* Make sure we can see the callouts on top of
each pin in case we have assigned title and/or
subtitle to each pin */
[annotationView setCanShowCallout:YES];
}


/* Now make sure, whether we have reused a pin or not, that
the color of the pin matches the color of the annotation */
annotationView.pinColor = senderAnnotation.pinColor;

result = annotationView;

6.5 Displaying Pins with Different Colors on a Map View | 447
return result;
}
- (void)viewDidLoad {
[super viewDidLoad];

/* Create a map as big as our view */
self.myMapView = [[MKMapView alloc]
initWithFrame:self.view.bounds];

self.myMapView.delegate = self;

/* Set the map type to Standard */
self.myMapView.mapType = MKMapTypeStandard;

self.myMapView.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;

/* Add it to our view */
[self.view addSubview:self.myMapView];

/* This is just a sample location */

CLLocationCoordinate2D location;
location.latitude = 50.82191692907181;
location.longitude = -0.13811767101287842;

/* Create the annotation using the location */
MyAnnotation *annotation =
[[MyAnnotation alloc] initWithCoordinates:location
title:@"My Title"
subTitle:@"My Sub Title"];

annotation.pinColor = MKPinAnnotationColorPurple;

/* And eventually add it to the map */
[self.myMapView addAnnotation:annotation];

}
- (void)viewDidUnload{
[super viewDidUnload];
self.myMapView = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation
:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
@end
And the results are shown here:
448 | Chapter 6: Core Location and Maps
Figure 6-5. A pin with an alternative color displayed on a map view
6.6 Displaying Custom Pins on a Map View
Problem

Instead of the default iOS SDK pins, you would like to display your own images as pins
on a map view.
6.6 Displaying Custom Pins on a Map View | 449
Solution
Load an arbitrary image into an instance of the UIImage class and assign it to the
image property of the MKAnnotationView instance that you return to your map view as
a pin:
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation{

MKAnnotationView *result = nil;

if ([annotation isKindOfClass:[MyAnnotation class]] == NO){
return result;
}

if ([mapView isEqual:self.myMapView] == NO){
/* We want to process this event only for the Map View
that we have created previously */
return result;
}

/* First typecast the annotation for which the Map View has
fired this delegate message */
MyAnnotation *senderAnnotation = (MyAnnotation *)annotation;

/* Using the class method we have defined in our custom
annotation class, we will attempt to get a reusable
identifier for the pin we are about to create */
NSString *pinReusableIdentifier =

[MyAnnotation
reusableIdentifierforPinColor:senderAnnotation.pinColor];

/* Using the identifier we retrieved above, we will
attempt to reuse a pin in the sender Map View */
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)
[mapView
dequeueReusableAnnotationViewWithIdentifier:
pinReusableIdentifier];

if (annotationView == nil){
/* If we fail to reuse a pin, then we will create one */
annotationView =
[[MKPinAnnotationView alloc] initWithAnnotation:senderAnnotation
reuseIdentifier:pinReusableIdentifier];

/* Make sure we can see the callouts on top of
each pin in case we have assigned title and/or
subtitle to each pin */
annotationView.canShowCallout = YES;

}

/* Now make sure, whether we have reused a pin or not, that
the color of the pin matches the color of the annotation */
annotationView.pinColor = senderAnnotation.pinColor;
450 | Chapter 6: Core Location and Maps

UIImage *pinImage = [UIImage imageNamed:@"BluePin.png"];
if (pinImage != nil){

annotationView.image = pinImage;
}

result = annotationView;

return result;
}
In this code, we are displaying an image named BluePin.png (in our application bundle)
for any pin that is dropped on the map. For the definition and the implementation of
the MyAnnotation class, refer to Recipe 6.5.
Discussion
The delegate object of an instance of the MKMapView class must conform to the
MKMapViewDelegate protocol and implement the mapView:viewForAnnotation: method.
The return value of this method is an instance of the MKAnnotationView class. Any object
that subclasses the aforementioned class, by default, inherits a property called image.
Assigning a value to this property will replace the default image provided by the Map
Kit framework, as shown in Figure 6-6.
6.6 Displaying Custom Pins on a Map View | 451
Figure 6-6. A custom image displayed on a map view
6.7 Converting Longitude and Latitude to a Meaningful
Address
Problem
You have the latitude and longitude of a spatial location and you want to retrieve the
address of this location.
452 | Chapter 6: Core Location and Maps
Solution
The process of retrieving a meaningful address using spatial coordinates, x and y, is
called reverse geocoding. Create and use an instance of the CLGeocoder class and provide
a completion block object, making sure that the block object has no return value and
accepts two parameters:

1. A placemarks array (of type NSArray) which will be set to the locations which
matched your search.
2. An error (of type NSError) which will get set to a valid error if the reverse geocoding
fails.
After instantiating an object of type CLGeocoder, we will use its reverseGeocodeLoca
tion:completionHandler: method in order to do the reverse geocoding.
The .h file of a simple view controller for this purpose is defined like so:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
@interface
Converting_Longitude_and_Latitude_to_a_Meaningful_AddressViewController
: UIViewController
@property (nonatomic, strong) CLGeocoder *myGeocoder;
@end
Then we will go ahead and synthesize our myGeocoder property:
@implementation
Converting_Longitude_and_Latitude_to_a_Meaningful_AddressViewController
@synthesize myGeocoder;

The next thing we will do is to do the reverse geocoding when our view loads:
- (void)viewDidLoad{
[super viewDidLoad];

CLLocation *location = [[CLLocation alloc]
initWithLatitude:+38.4112810
longitude:-122.8409780f];
self.myGeocoder = [[CLGeocoder alloc] init];

[self.myGeocoder
reverseGeocodeLocation:location

completionHandler:^(NSArray *placemarks, NSError *error) {

if (error == nil &&
[placemarks count] > 0){

6.7 Converting Longitude and Latitude to a Meaningful Address | 453

×