Challenge: Reverse Geocoding


#1

Hi,

I was able to set up the MKReverseGeocoder + Delegate. I implemented both required methods and I am able to print the City and State on the console with NSLog out of didFindPlacemark. So far so good.

I tried to get the information out of didFindPlacemark (City/State) and the CLLocationManager together to set up the MapPoint with the correct text. The problem that I have is that I don’t know when the didFindPlacemark method is called. So the MapPoint is already set when I have the City/State information.

Thanks for any suggestions.


#2

You will have to play with the flow of the application a bit to get it in there.

As I see it, you have two choices:

  1. Delay adding the MapPoint to the map until you have determined the additional information.
  2. Update the MapPoint after you have determined the additional information.

The first is certainly more straightforward, but, in a production application I’d hate to leave the user waiting any longer than they have to to see anything, and would opt for the second, more difficult choice.

But, as it is a challenge, I would hate to spoil anyone else’s (including your!) fun. :slight_smile:


#3

In keeping with Joe’s suggestion #2 (ie, fill in the Reverse Geocoding info after the mapPoint has already been initially placed, thereby minimizing wait time for the user to see the annotation), one way to do this is to grab the annotation of interest from mapView’s array of annotations when the reverseGeocoder:didFindPlacemark routine is called, and then update its title. Here is a verbose (and not very efficient) way to do that:

- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder 
	   didFindPlacemark:(MKPlacemark *)placemark
{
	NSLog(@"Address is %@ %@, %@ %@", [placemark subThoroughfare],
		  [placemark thoroughfare], [placemark locality],
		  [placemark administrativeArea]);
		  
	NSArray *annotations = [mapView annotations];
	NSLog(@"Number of annotations = %d", [annotations count]);
	
	// turn the location info into a string
	NSString *title = [NSString localizedStringWithFormat:@"%@, %@",
					   [placemark locality], [placemark administrativeArea]];
	
	// use the latest annotation to mark with geocode
	[[annotations lastObject] setTitle:title];
	NSLog(@"Title = %@", [[annotations lastObject] title]);
}

As you can see, this code assumes that the “lastObject” in the annotations array is the one you care about. This may be dangerous depending upon your program flow, but I post this just to give you the idea that what you need to do is go into mapView’s annotations array and update the title.

Good Luck!


#4

So my question is … who’s the delegate of MKReverseGeocoder?

It seems like there are two ways (hell, probably more) to go here…

It might be reasonable to have each MapPoint own a MKReverseGeocode object, and if the user wants the subtitle to be city and state to then have a MapPoint instance method that gets the ball rolling on an as-needed basis. To the best of my understanding, MapPoint would then have to be the delegate class for MKReverseGeocoder.

Also, you could have you AppDelegate be the delegate class for MKReverseGeocoder and make it responsible for setting the subtitle of each annotation of the map view. It could set the subtitles as you build the annotation object, or after the object has been added to the array (as Joe whets our appetite earlier…)

I am curious to know which approach would be used more frequently, and why. Would nearly all programmers stay away from option one because it makes MapPoint less re-usable? Or maybe because your Model objects aren’t suppose to be delegates? I don’t know.

At this point, the book doesn’t provide me any theory (I know, it’s super boring!) to understand why I should choose one over the other. So I figured I’d open up that can of worms!

MKReverseGeocoder could be a really large object (unbeknownst to me), and so creating a new one for each of my MapPoints in the annotations array may be ludicrous. Or it could be that I can’t fire up several reverse look ups without bringing my app to it’s knees. I was hoping to get some insight into how to think and avoid such pitfalls as I tackle these tasks.

I think the challenge I’ll issue myself is to code both of them up and see if I can tell any obvious benefits or drawbacks. While I work on that, I’d be very interested to hear how everyone weighs in.


#5

Well, spoiler alert, here is how I would solve it:

In WhereamiAppDelegate.m’s locationManager:didUpdateToLocation:fromLocation:

       MapPoint *mp = [[MapPoint alloc] 
                        initWithCoordinate:[newLocation coordinate] 
                                     title:[locationTitleField text]];
// Add    
	MKReverseGeocoder *rg = [[MKReverseGeocoder alloc] initWithCoordinate:[newLocation coordinate]];
	[rg setDelegate:mp];
	[rg start]; 
// End Add
	[mapView addAnnotation:mp];

Add MKReverseGeocoderDelegate protocol to MapPoint.

@interface MapPoint : NSObject <MKAnnotation, MKReverseGeocoderDelegate>

Add the following ivar to MapPoint

	MKPlacemark *placemark;

Add the following to MapPoint.m

- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
{	
	[geocoder release];
}
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)p
{
	placemark = [p retain];
	[geocoder release];
}
- (NSString *)subtitle
{
	return [NSString stringWithFormat:@"%@, %@", [placemark locality], [placemark administrativeArea]];
}

#6

Thanks for the spoiler. It really helped me to get a better understanding.

Just coming from the memory management section of the book I have a question:

Shouldn’t we add a

statement in the dealloc of MapPoint.m?

Thanks
Tom


#7

Yup, forgot to mention that part, do release the placeholder in dealloc.


#8

I’m confused with the last method in your code “(NSString *)subtitle”. I know it is needed but I don’t understand why??? mp never calls it.

In my MapPoint.m I have an NSString named subtitle, which I needed to set the date in the previous challenge. Is making a method that is the name of a class variable the same as doing a setSubtitle call???

Very confused with this method. Please help…


#9

subtitle is an optional method on the MKAnnotation protocol.

When an object is added to a MKMapView as an annotation, that object must have a coordinate method - the map view will, at some point, ask every annotation object for its coordinate in order to place a pin on the map at the right spot.

There are two optional getter methods in the protocol. When the map view goes to display an annotation, it checks to see if that annotation object has implemented these two optional getter methods: title and subtitle. If the annotation object does implement these methods, the map view sends the message title and subtitle to the annotation object and uses the resulting string to display on the pin when tapped.

If the annotation object does not implement the title method, no title is displayed; same for subtitle.


#10

Okay, I understand your explanation. One problem I have is that you say if these methods are not implemented then no title/subtitle will be displayed. However, before doing this challenge, I had neither implemented in my MapPoint class. Yet, I was still able to display a subtitle (the date) and a title (the string from the TextField). Is this because I named these variables “title” and “subtitle” and MKAnnotationView automatically looks for these to variables???

NOTE: I do understand that we have getTitle and getSubtitle (through property and synthesize), but this is different from “(NSString *)subtitle”


#11

Ah, that’s where your confusion is coming from - you don’t have getSubtitle or getTitle. When you synthesize a property, the name of the getter method is the name of the property. Therefore, the synthesize property title gives you a method named title and same goes for subtitle.


#12

OK, I have a question on the spoiler with relation to memory management, specifically on the following code:
MapPoint methods:

- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)p
{
   placemark = [p retain];
   [geocoder release];
}

-(void)dealloc {
	
	[title release];
	[subtitle release];
	[placemark release];
	[super dealloc];
	
}

I see that we’re retaining “p” in reverseGeocoder:didFindPlacemark: and assigning p to placemark, but we’re releasing placemark in dealloc.

When does “p” get released?


#13

You don’t have responsibility for p; that pointer is coming from the object that sent you this message. You take ownership of that object and assign it to the variable placemark. Your only responsibility is to release your hold on placemark.


#14

Doesn’t having the the MapPoint as the delegate violate the MVC architecture? It seems to me that this allows a model direct access to a view. Shouldn’t we implement a separate delegate class, or something?
Am I missing a point here?


#15

Setting MapPoint as the delegate of MKReverseGeocoder is not a violation of MVC, because the geocoder isn’t a view object; it acts as a controller object. It’s responsibility is to take data from the internet, parse it, and then hand that over to a delegate object.

You’re taking model objects (CLLocationCoordinate2D), manipulating them with the geocoder controller object, and the delegate receives a message when the geocoder is completed so that MapPoint can append new data (MKPlacemark) to its model object.

It’s a little tricky, because there isn’t an explicit view object representing the push-pins that we work with in Whereami. You feed the MKMapView a bunch of model objects (MapPoints) and because those objects conform to the MKAnnotation protocol the map view knows how to display them as push-pins with titles and subtitles.


#16

I was visualizing the MapPoints as view rather than controller objects. Thank you for the clarification.