Playing with iOS MapKit Part 2: Reverse Geocoding and Custom Annotation Callouts

Playing With MapKit

Part 1 MapKit Tutorials 

Part 2 Reverse Geocoding and Custom Annotation Callouts

Part 3 Making it Pretty

Part 4 Race Conditions, Other Bugs, and Wrap-up

After following the tutorials in part 1, I wanted to keep exploring MapKit. As reverse geocoding was my next topic, I wanted to show the address of the current location in a callout (view that pops up above the pin).

To do this, I’ll need:

  1. A way to know when the current location was tapped
  2. A way to know the address of the current location
  3. A way to display the address in a callout

1. How do I know when the current location is tapped?

MKMapViewDelegate has a method called:

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

that is called when an annotation is tapped.

How do I tell that this annotation is my current location?

Notice that the annotation is any class that conforms to MKAnnotation.

Current location comes in a special class called MKUserLocation so you just need to test if the annotation is that class.

if ([annotation isKindOfClass:[MKUserLocation class]]
) {
...
}

Within this if statement, I need to turn the currentLocation into an address.

This MKUserLocation has a CLLocation property, a BOOL property for whether it’s updating, a CLHeading property, a title and a subtitle.

2. How do I find the address with coordinates? (Reverse Geocoding)

Apple provides a CLGeocoder class that has the method

- (void)reverseGeocodeLocation:(CLLocation *)location 
completionHandler:(CLGeocodeCompletionHandler)
completionHandler

All you need is the location which comes from MKUserLocation’s location property.

What does the completion handler return?

NSArray *placemarks, NSError *error

placemarks is an NSArray of CLPlacemarks. Each placemark has an addressDictionary. The key @”FormattedAddressLines” returns an array of formatted address lines.

Typically, there are three lines. One for address, another for city, state, and zip code, and one more for the country. In some situations, there are four lines where the first is the name of a venue.

3. How do I display the address?

One way is to set the callout for the annotation is to set the title of the annotation to the text you want in the callout. For example:

self.address = [NSString stringWithFormat:@" %@\n %@\n 
%@", street, cityState, country];
[(MKUserLocation *)annotation setTitle:self.address];

When I do this, the problem is that there is only one line and the rest of the address gets cut off.

Cut Off Callout

Adding line breaks to the title did not make it appear on additional lines, but only displayed the first line instead.

Callout title with line breaks

Messing with the left and right accessory buttons did not help either.

Callout with left and right accessory views colored red and blue
Callout with left and right accessory views colored red and blue

With no way to set more than two lines of text, I had to make a custom MKPinAnnotationView.

How do I make a custom annotation callout?

Since the callout is a property of the MKPinAnnotationView, I had to make a subclass of the MKPinAnnotationView and overwrite the setSelected: animated method.

- (void) setSelected:(BOOL)selected animated:(BOOL)animated
  1. Draw a label
  2. Set the text of the label with the annotation’s title
  3. Place a view as a subview of the MKPinAnnotationView subclass (which I called MultilineAnnotationView)
  4. Add the label as a subview of view.
setSelected
Why MKPinAnnotationView and not the default user location view?

The default user location view is a class that you cannot access or subclass.

Custom Callout View
The custom callout works! (but not perfectly)

Up next, making things look pretty.