Challenge: Annotation Extras


#1

I also took on the challenge, and I think my solution was pretty elegant. I think I got all the memory issues correct, but if anyone notices a mistake, I would like to know about it.

I made changes in three places; MapPoint.h, MapPoint.m, and the locationManager method where MapPoint is used.

First my MapPoint.h changes.

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

// Challenge: Adding the date to the mappoint
// I’m going to take advantage of the “Subtitle” field that is built in the
// MKAnnotation protocol. I add *subtitle to the interface, declare
// a property, and add an additional parameter to the method

@interface MapPoint : NSObject
{
NSString *title;
NSString *subtitle;
CLLocationCoordinate2D coordinate;
}

@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;

  • (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t subtitle:(NSString *)st;

@end
[/code]

Next my MapPoint.m changes:

[code]#import “MapPoint.h”

@implementation MapPoint

// Challenge: Adding the date to the mappoint
// Here I need to add subtitle to the @synthesize, match the method parameters
// to the new one I declared in the .h file, and then call the “setSubtitle”
// accessor that is built into MKAnnotation (just like setTitle)
// Also need to add a release for it in the dealloc method

@synthesize coordinate, title, subtitle;

  • (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t subtitle:(NSString *)st
    {
    [super init];
    coordinate = c;
    [self setTitle:t];
    [self setSubtitle:st];
    return self;
    }

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

@end
[/code]

Note that the changes to MapPoint are only to add the additional subTitle parameter in conformance with the MKAnnotation protocol. Finally, here are the changes to the method inside WhereamiAppDelegate.m:

[code]- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSLog(@"%@", newLocation);

// How many seconds ago was this new location created?
NSTimeInterval t = [[newLocation timestamp] timeIntervalSinceNow];

// CLLocationManagers will return the last found location of the device first,
// you don't want that data in this case.
// If this location was made more than 3 minutes ago, ignore it.
if (t < -180) {
	//This is cached data, you don't want it, keep looking
	return;
}

// Get the date
// Note that the date was included in the location object when created, it can be referenced
// using the timestamp property
// What we would do if we wanted to keep this date around is use something like:
// NSDate *today = [newLocation timestamp];
// But then I would need to allocate a pointer to an NSDate object, instead I will
// pass [newLocation timestamp] into my formatter

// The date is unusable in it's current state, it needs to be formatted into a readable string
// We use a dateFormatter object that can create a usable string when passed an arbitrary date
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 

// Set the time style to NONE, and the date style to Medium
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];

// I'm going to take a short cut and not bother with Locale, being lazy

// Having added the Subtitle parameter to MapPoint, I'm using my dateFormatter object,
// using the stringfromdate method to extract the string.  Since the location timestamp
// is already an NSDate object, I'm pulling it directly without creating a local pointer
// to an NSDate
MapPoint *mp = [[MapPoint alloc] initWithCoordinate:[newLocation coordinate] 
											  title:[locationTitleField text]
										   subtitle:[dateFormatter stringFromDate:[newLocation timestamp]]];
[ptrInstanceMapView addAnnotation:mp];

// release the two pointers that were allocated in this routine
[mp release];
[dateFormatter release];

[self foundLocation];

}
[/code]


#2

Great post and great comments. I really appreciate the code samples.
I will continue to complete most (if not all) of the challenges … and I will first look to see if you posted anything first because you seem knowledgeable.
That being said … my XCode complains on this: [ptrInstanceMapView addAnnotation:mp]; (use of undeclared identifier ptrInstanceMapView)

where and how should I declare this?
I noticed MapView was declared in WhereamiAppDelegate.h (as IBOutlet MKMapView)

Also, I noticed a missing line of code in the third example
[mapView addAnnotation:mp];
Should that code still be in the file?

I was also wondering if you can hover over the error and have it give you suggestions (like eclipse)


#3

Yeah, I can see where things would hiccup for you. I renamed the pointer in the book’s example.

Don’t ask, I was trying to fix an error in the example code they gave and did it the hard way.

In the book, the WhereamiAppDelegate.h includes:

[code]@interface WhereamiAppDelegate : NSObject <UIApplicationDelegate, CLLocationManagerDelegate, MKMapViewDelegate>
{
UIWindow *window;

CLLocationManager *locationManager;

IBOutlet MKMapView *MapView;
IBOutlet UIActivityIndicatorView *activityIndicator;
IBOutlet UITextField *locationTitleField;

}[/code]

I renamed MapView to ptrInstanceMapView. If you change it back to MapView, then everything should compile.


#4

OK, thanks.
I figured it was something like that.

My next question is … so what should the end result of the app look like?
I made the changes like you did, but the resulting simulator looks exactly the same as before modifications to the code.
Should we be seeing the date somewhere? I see the red pin, but nothing else.

I wish the book would include a small screen shot of what the end results would look like for applicable challenges.

Thanks again. I will be sure to watch for you in future challenges.
Jerry


#5

If you touch the red pin, then the annotation should appear over the pin.

I’m running this on an iPod Touch, so I get the following sequence:

  1. The map appears over my house with a text box on top
  2. I enter “Home” into the box and hit search
  3. It thinks a little bit, then drops a red pin on my location, putting the text box back up
  4. If I touch the red pin, it will display “Home” with the date right below it

If you keep having trouble, I will post my entire working code


#6

Thanks for the input.
Well, All of my code is exactly as the book, yet I do not get that text over my location.
It would be great if you would post your code … is it different from the book?

Jerry


#7

I don’t think so, other than the fixes to let it work on iOS 4.3. I don’t know if you are trying to use the simulator, but I found this code only worked on the device. Something to do with getting location to realize there has been an update.

Here is MapPoint.h:

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

// Challenge: Adding the date to the mappoint
// I’m going to take advantage of the “Subtitle” field that is built in the
// MKAnnotation protocol. I add *subtitle to the interface, declare
// a property, and add an additional parameter to the method

@interface MapPoint : NSObject
{
NSString *title;
NSString *subtitle;
CLLocationCoordinate2D coordinate;
}

@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;

  • (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t subtitle:(NSString *)st;

@end[/code]

Here is MapPoint.m:

[code]#import “MapPoint.h”

@implementation MapPoint

// Challenge: Adding the date to the mappoint
// Here I need to add subtitle to the @synthesize, match the method parameters
// to the new one I declared in the .h file, and then call the “setSubtitle”
// accessor that is built into MKAnnotation (just like setTitle)
// Also need to add a release for it in the dealloc method

@synthesize coordinate, title, subtitle;

  • (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t subtitle:(NSString *)st
    {
    [super init];
    coordinate = c;
    [self setTitle:t];
    [self setSubtitle:st];
    return self;
    }

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

@end[/code]

Here is WhereamiAppDelegate.h:

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

@interface WhereamiAppDelegate : NSObject <UIApplicationDelegate, CLLocationManagerDelegate, MKMapViewDelegate>
{
UIWindow *window;

CLLocationManager *locationManager;

IBOutlet MKMapView *ptrInstanceMapView;
IBOutlet UIActivityIndicatorView *activityIndicator;
IBOutlet UITextField *locationTitleField;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;

  • (void)findLocation;
  • (void)foundLocation;

@end[/code]

And here is WhereamiAppDelegate.m

[code]#import “WhereamiAppDelegate.h”
#import “MapPoint.h”

@implementation WhereamiAppDelegate

@synthesize window;

  • (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {

    // Create location manager object -
    locationManager = [[CLLocationManager alloc] init];

    // Make this instance of WhereamiAppDelegate the delegate
    // it will send its messages to our WhereamiAppDelegate
    [locationManager setDelegate:self];

    // We want all results from the location manager
    [locationManager setDistanceFilter:kCLDistanceFilterNone];

    // And we want it to be as accurate as possible
    // regardless of how much time/power is takes
    [locationManager setDesiredAccuracy:kCLLocationAccuracyBest];

    // Tell our manager to start looking for its location immediately
    [ptrInstanceMapView setShowsUserLocation:YES];

    [window makeKeyAndVisible];

    return YES;
    }

  • (void)findLocation
    {
    [locationManager startUpdatingLocation];
    [activityIndicator startAnimating];
    [locationTitleField setHidden:YES];
    }

  • (void)foundLocation
    {
    [locationTitleField setText:@""];
    [activityIndicator stopAnimating];
    [locationTitleField setHidden:NO];
    [locationManager stopUpdatingLocation];
    }

  • (BOOL)textFieldShouldReturn:(UITextField *)tf
    {
    [self findLocation];
    [tf resignFirstResponder];
    return YES;
    }

  • (void)mapView:(MKMapView *)ptrMV didAddAnnotationViews:(NSArray *)views
    {
    MKAnnotationView *annotationView = [views objectAtIndex:0];
    id mp = [annotationView annotation];
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([mp coordinate], 250, 250);
    [ptrMV setRegion:region animated:YES];
    }

  • (void)mapView:(MKMapView *)ptrMV didUpdateUserLocation:(MKUserLocation *)userLocation
    {
    CLLocationCoordinate2D loc = [userLocation coordinate];
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(loc, 250, 250);
    [ptrMV setRegion:region animated:YES];
    }

  • (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
    fromLocation:(CLLocation *)oldLocation
    {
    NSLog(@"%@", newLocation);

    // How many seconds ago was this new location created?
    NSTimeInterval t = [[newLocation timestamp] timeIntervalSinceNow];

    // CLLocationManagers will return the last found location of the device first,
    // you don’t want that data in this case.
    // If this location was made more than 3 minutes ago, ignore it.
    if (t < -180) {
    //This is cached data, you don’t want it, keep looking
    return;
    }

    // Get the date
    // Note that the date was included in the location object when created, it can be referenced
    // using the timestamp property
    // What we would do if we wanted to keep this date around is use something like:
    // NSDate *today = [newLocation timestamp];
    // But then I would need to allocate a pointer to an NSDate object, instead I will
    // pass [newLocation timestamp] into my formatter

    // The date is unusable in it’s current state, it needs to be formatted into a readable string
    // We use a dateFormatter object that can create a usable string when passed an arbitratry date
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];

    // Set the time style to NONE, and the date style to Medium
    [dateFormatter setTimeStyle:NSDateFormatterNoStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];

    // I’m going to take a short cut and not bother with Locale, being lazy

    // Having added the Subtitle parameter to MapPoint, i’m using my dateFormatter object,
    // using the stringfromdate method to extract the string. Since the location timestamp
    // is already an NSDate object, I’m pulling it directly without creating a local pointer
    // to an NSDate

    MapPoint *mp = [[MapPoint alloc] initWithCoordinate:[newLocation coordinate]
    title:[locationTitleField text]
    subtitle:[dateFormatter stringFromDate:[newLocation timestamp]]];

    [ptrInstanceMapView addAnnotation:mp];
    [mp release];
    mp = nil;
    [dateFormatter release];
    [self foundLocation];
    }

  • (void)locationManager:(CLLocationManager *)manager
    didFailWithError:(NSError *)error
    {
    NSLog(@“Could not find location: %@”, error);
    }

  • (void)dealloc
    {
    [locationManager release];
    [window release];
    [super dealloc];
    }

@end[/code]


#8

Hi there,

I’m just getting started, so please excuse my n00biness… I just read the solution above by dthompson32, and while I can pretty much understand it all, it seems like a lot of work. Here’s what I did :

  1. Went to the “foundlocation:” method which is where *mp instance of MapPoint gets created and the title is decided.

  2. Get the current date and assign a pointer to it called *now by using this line :

  1. Concatenate the title (what the user types in) and the date (*now) together with this line :
  1. Create the *mp instance of MapPoint using the original code in the textbook:

MapPoint *mp = [[MapPoint alloc] initWithCoordinate:coord title:test];

A couple of things :

#1 - I could not for the life of me find out how to concatenate two strings together. I’m used to programming in PHP or Javascript where it’s easy as pie. Did I do this correctly? Is there a better or easier way? (ie - String3 = String 1 + String 2). I’m not really that used to working with String objects, but I’m learning.

#2 - I realize that this doesn’t format the Date, it just spits out the entire Time+Date stamp. I assume I need to look into the NSDateFormatter a bit more to get this done. Is there a quick and easy way I can do it?

#3 - How do I know where to release my *test instance? I tried adding [test release] after the *mp instance has been created, because it seems like after creation, *test is no longer needed by anything. Am I wrong? It crashes my app when I release it. Any advice on this would be greatly appreciated. Still wrapping my head around memory management.

Thanks for any help anyone can give.


#9

So this does not work on the simulator. It needs to be done on a device. :wink: