Problem with reverseGeocode Challenge


#1

I think Ive about got this challenge licked but I’m stuck on something really simple. How do you append strings in Objective C? Here is my current solution for any interested. I also welcome constructive feedback as well if I’ve done something wrong or there is a better way.

I have determined that when MKReverseGeocoder is implemented properly you will have a struct called a MKPlacemark with the various items of a location(City, state, region, etc) as it’s data members. Then we must extract the relevant data members by parsing them to a string to add to our MapPoint annotations.

Here is my header file

[code]#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>

@interface WhereAmIAppDelegate : NSObject <UIApplicationDelegate, CLLocationManagerDelegate, MKMapViewDelegate, UITextFieldDelegate, MKReverseGeocoderDelegate>
// MKReverserGeocoderDelegate protocol added
{
CLLocationManager *locationManager;
// reverseGeocoder will be treated like the location manager – functions will be added to implementation to receive messages from this object
MKReverseGeocoder *reverseGeocoder;
// this string will be used to build geocode string – may be made into a local variable for function later
NSString *thePlace;
IBOutlet MKMapView *worldView;
IBOutlet UIActivityIndicatorView *activityIndicator;
IBOutlet UITextField *locationTitleField;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
-(void)findLocation;
-(void)foundLocation:(CLLocation *)loc;
@end[/code]
Next I added these delegate protocol functions to the end of my implementation file. This is where I’m having trouble trying to append strings. Ive tried with NSSTring and NSMutableStrings both to no luck so far. Help?!?!

[code]- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error{
NSLog(@“It failed to reverseGeocode location!”);
}

  • (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark{
    thePlace = [placemark locality];
    NSLog(@"%@", thePlace);
    //NSLog(@"%@", thePlace);
    //[thePlace appendString:@“Stuff!”]; // need to append administrativeArea to the place
    }[/code]
    Finally I have determined the best time and place to get the reverse geolocation info is in our foundLocation function, as the documentation states the reverseGeocoder should be used sparingly and we really only need it when annotating the map so I start it in there. Of course I need to figure out the string append thing and then I think I’ll be done after I finish in here.

[code]-(void)foundLocation:(CLLocation *)loc
{
CLLocationCoordinate2D coord = [loc coordinate];

// set up reverse geocoder
reverseGeocoder = [[MKReverseGeocoder alloc] initWithCoordinate:coord];
// set this implimentation as the delegate
[reverseGeocoder setDelegate:self];
// start the reverseGeocode
[reverseGeocoder start];
// thePlace has a value now and we are ready to add it to input and creat our mapPoint
NSLog(@"The place = %@", thePlace);


// append thePlace to locationTitleField text before creating mp 


// Create an instance of MapPoint with the current data
MapPoint *mp =[[MapPoint alloc] initWithCoordinate:coord
                                             title:[locationTitleField text]];

…rest of code from book here…
}
[/code]

I have this much working such that when you type in a name for the annotation, in addition to the MapPoint annotation being dropped on the map my console is logging my home city as well from the reverseGeocoder:didFindPlacemark: method. So now I know I can build the string I want from the MKPlacemark. My plan then was to append it to the input for the name of place typed in so the annotation would read something like “My House - Richmond, Virginia”. I am however stumped on where to go from here. Any help or feedback is appreciated.


#2

Not sure how I missed this the first time, but the following if statement should be placed in code right after sending the start msg to reverseGeocoder. It ensures the reverseGeocode is complete before moving on to complete the mapPoint annotation.

    // we are going to add our reverse geocode challenge in a string to the user input from text field
    // so first we need to make sure that the reversegeocoding is complete and the place has been given a value
    if (![self thePlace])
    {
        NSLog(@"PlaceMark not available yet");
        return;
    }

#3

In reverseGeocoder:didFindPlacemart: I wrote:

This will result in a string that reads: - Durham, North Carolina (in my case).

Then, once the placemark is available, I used:

I was having trouble with thePlace always being null until I saw your if (!thePlace) statement. I still don’t understand why it works - it seems like if thePlace isn’t set, all of the annotation code will get skipped - but it doesn’t. Can someone explain why not?


#4

This is pretty scary in terms of memory management.

You don’t own the reference — it was just part of a passed parameter — but you’re treating it as though you do, declaring the instance variable, setting it in one routine without retaining, and using it in another. If you want to own it, then retain it…but be mindful of whatever value is already in there. So that means setting up and synthesizing a property and using the setter. Consider reviewing chapter 3 on memory management.

As far as appending strings, @squarehippo showed both -stringByAppendingString: as well as +stringWithFormat:. Another possibility is just to skip the appending and take advantage of MKAnnotation’s -subtitle message. (Declare and synthesize a property named “subtitle” in MapPoint, and use the setter to set its value to thePlace.)

I’m sure there are better ways of implementing the challenge, but for what it’s worth, here’s how I did it.

[code]

  • (void) foundLocation: (CLLocation *) location
    {
    CLLocationCoordinate2D coordinate = [location coordinate];

    // add the point to the map
    //
    MapPoint * mapPoint = [[MapPoint alloc] initWithCoordinate: coordinate
    title: [locationTitleField text]
    ];

    [worldView addAnnotation: mapPoint];
    [mapPoint release];

    // where are we in English?
    //
    if( reverseGeocoder == nil ) {
    reverseGeocoder = [[MKReverseGeocoder alloc] initWithCoordinate: coordinate];
    [reverseGeocoder setDelegate: self];
    [reverseGeocoder start];
    }

    // zoom the displayed map to this point
    //
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance( coordinate, 250, 250 );
    [worldView setRegion:region animated:YES];

    // cleanup
    //
    [activityIndicator stopAnimating];

    [locationTitleField setText: @""];
    [locationTitleField setHidden: NO];

    [locationManager stopUpdatingLocation];
    }
    [/code]reverseGeocoder can get the info for only one place at a time, so the code ignores the request if it’s in the middle of an existing operation.

Also, I had started the activityIndicator animating in -application:didFinishLaunchingWithOptions: (since it takes a little time to set and zoom in to the blue dot) so that animation is stopped here.

[code]
#pragma mark -
#pragma mark MKReverseGeocoder delegate methods

  • (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
    {
    NSLog( @“found location: %@, %@”, [placemark locality], [placemark administrativeArea] );

    MKMapPoint originalPoint = MKMapPointForCoordinate( [geocoder coordinate] );

    for( MapPoint * point in [worldView annotations] ) {

      if( ![point isKindOfClass: [MapPoint class]] ) {
          continue;
      }
    
      MKMapPoint annotationPoint = MKMapPointForCoordinate( [point coordinate] );
      if( MKMapPointEqualToPoint( originalPoint, annotationPoint )) {
          [point addPlacemarkToAnnotation: placemark];
      }
    

    }

    [reverseGeocoder release]; reverseGeocoder = nil;
    }

  • (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
    {
    NSLog( @“MKReverseGeocoder failed: %@”, error );

    [reverseGeocoder release]; reverseGeocoder = nil;
    }
    [/code]It’s possible to have multiple annotations, so the code cycles through them to find The One it cares about. This wouldn’t scale to hundreds of annotations, so maybe it would be better to just keep a separate reference to the MapPoint being processed by the reverse geocoder.

I made two mistakes in a prior version of the above. The lesser mistake was that I checked to see if it was the same coordinate by comparing the latitude and longitude values directly. That’s subject to error because they’re floating point numbers. Fortunately I stumbled upon the routine intended for comparison (which I figured had to exist).

The other, bigger mistake I made previously was in not verifying the point being checked was actually a MapPoint. The app was working fine in the simulator but crashing on the device. That turned out to be because the initial blue dot in the simulator is for Apple’s HQ, but on the device it’s my actual location. The first point encountered when running on the device was for that blue dot, of type MKUserLocation, rather than the annotation I had added, of type MapPoint. I googled around to find the Cocoa equivalent of Java’s instanceof operator to be sure only MapPoint instances are considered.

The rest is just adding the support to MapPoint.
MapPoint.h:

    NSString * subtitle;
...
- (void) addPlacemarkToAnnotation: (MKPlacemark *) placemark;
...
@property (nonatomic, copy) NSString * subtitle;

MapPoint.m:

@synthesize subtitle;
...
- (void) addPlacemarkToAnnotation: (MKPlacemark *) placemark
{
    NSString * newSubtitle = [NSString stringWithFormat: @"%@, %@", [placemark locality], [placemark administrativeArea]];
    
    [self setSubtitle: newSubtitle];
}

#5

I’m having a lot of problems with this challenge. I’ve used @gc3182 approach a a last resort and it’s the only one that’s working so far except that instead of the city and state I get null, null.
Anybody can help me and explain why I get that result?

Thanks