So, for the past few days I have been struggling to understand how to implement a simple MKMapView with some custom annotations without crashing my application in the process. Unfortunately I have been unable to determine what I'm doing wrong and am becoming increasingly frustrated in the process.
What I'm trying to accomplish should be relatively simple. I'm trying to create a new object w/ a location associated with it. To do this I have a view controller for creating the object. I want the user to be able to cancel out of the view controller at any time if they so desire, but in order to save the object they must first provide a location and a name for it. The name will be taken via a UITextField while the location will be obtained via the MKMapView.
So here is what happens... Whenever I open up the New Object View Controller, it updates the location and whatnot. If I try to click cancel, it crashes. In an attempt to simplify the problem I removed the code for updating the location and moving the annotation pin, so you won't see that below.
Here is some of the code I'm using in addition to an example of the stack trace I encounter after the crash. Any help you can offer would be greatly appreciated. Thanks!
Before I show you my code, here is the stack trace I end up with:
Stack Trace After Crash
#1 0x30c4c8b8 in -[UIImageView stopAnimating]
#2 0x30c4c810 in -[UIImageView dealloc]
#3 0x32d86640 in -[NSObject release]
#4 0x32d198ac in -[MKAnnotationView dealloc]
#5 0x32d86640 in -[NSObject release]
#6 0x30bfab34 in -[UIView(Hierarchy) removeFromSuperview]
#7 0x30c4ca24 in -[UIView dealloc]
#8 0x32ce881c in -[MKOverlayView dealloc]
#9 0x32d86640 in -[NSObject release]
#10 0x30bfab34 in -[UIView(Hierarchy) removeFromSuperview]
#11 0x30c4ca24 in -[UIView dealloc]
#12 0x30cbb878 in -[UIScrollView dealloc]
#13 0x32d179c4 in -[MKScrollView dealloc]
#14 0x32d86640 in -[NSObject release]
#15 0x30bfab34 in -[UIView(Hierarchy) removeFromSuperview]
#16 0x30cbb4a8 in -[UIScrollView removeFromSuperview]
#17 0x30c4ca24 in -[UIView dealloc]
#18 0x32d86640 in -[NSObject release]
#19 0x30bfab34 in -[UIView(Hierarchy) removeFromSuperview]
#20 0x30c4ca24 in -[UIView dealloc]
#21 0x32cc579c in -[MKMapView dealloc]
#22 0x32d86640 in -[NSObject release]
#23 0x30bfab34 in -[UIView(Hierarchy) removeFromSuperview]
#24 0x30c4ca24 in -[UIView dealloc]
#25 0x32d86640 in -[NSObject release]
#26 0x30bfab34 in -[UIView(Hierarchy) removeFromSuperview]
#27 0x30c4ca24 in -[UIView dealloc]
#28 0x32d86640 in -[NSObject release]
#29 0x30bfab34 in -[UIView(Hierarchy) removeFromSuperview]
#30 0x30c4ca24 in -[UIView dealloc]
#31 0x32d86640 in -[NSObject release]
#32 0x30bfab34 in -[UIView(Hierarchy) removeFromSuperview]
#33 0x30c4ca24 in -[UIView dealloc]
#34 0x32d86640 in -[NSObject release]
#35 0x33f70996 in NSPopAutoreleasePool
#36 0x33e99104 in run_animation_callbacks
#37 0x33e98e6c in CA::timer_callback
#38 0x32da44c2 in CFRunLoopRunSpecific
#39 0x32da3c1e in CFRunLoopRunInMode
#40 0x31bb9374 in GSEventRunModal
#41 0x30bf3c30 in -[UIApplication _run]
#42 0x30bf2230 in UIApplicationMain
#43 0x00002450 in main at main.m:14
CustomAnnotation.h:
@interface CustomAnnotation : NSObject <MKAnnotation,
MKReverseGeocoderDelegate>
{
@private
MKReverseGeocoder* _reverseGeocoder;
MKPlacemark* _placemark;
@public
CLLocationCoordinate2D _coordinate;
NSString* _title;
}
//Note: Property for CLLocationCoordinate2D coordinate is declared in MKAnnotation
@property (nonatomic, retain) NSString* title;
@property (nonatomic, retain) MKPlacemark* placemark;
-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinate
title:(NSString*)title;
-(void) setCoordinate:(CLLocationCoordinate2D)coordinate;
@end
CustomAnnotation.m
@implementation CustomAnnotation
@synthesize coordinate = _coordinate; // property declared in MKAnnotation.h
@synthesize title = _title;
@synthesize placemark = _placemark;
-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinate
title:(NSString*)title
{
if(self = [super init])
{
_title = [title retain];
[self setCoordinate:coordinate];
_placemark = nil;
}
return self;
}
#pragma mark -
#pragma mark MKAnnotationView Notification
- (void)notifyCalloutInfo:(MKPlacemark *)newPlacemark {
[self willChangeValueForKey:@"subtitle"]; // Workaround for SDK 3.0, otherwise callout info won't update.
self.placemark = newPlacemark;
[self didChangeValueForKey:@"subtitle"]; // Workaround for SDK 3.0, otherwise callout info won't update.
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:@"MKAnnotationCalloutInfoDidChangeNotification" object:self]];
}
#pragma mark
#pragma mark -
#pragma mark Reverse Geocoder Reset Procedure
- (void)resetReverseGeocoder
{
if(_reverseGeocoder != nil)
{
//If the reverse geocoder already exists, check to make sure it isn't querying. Cancel query if it is.
if([_reverseGeocoder isQuerying])
{
[_reverseGeocoder cancel];
}
//Before releasing the reverse geocoder, set it's delegate to nil just to be safe
[_reverseGeocoder setDelegate:nil];
//Release the current reverse geocoder
[_reverseGeocoder release];
_reverseGeocoder = nil;
}
}
#pragma mark
#pragma mark -
#pragma mark Set Coordinate Procedure
- (void)setCoordinate:(CLLocationCoordinate2D)coordinate {
_coordinate = coordinate;
//We only want to be reverse geocoding one location at a time, so make sure we've reset the reverse geocoder before starting
[self resetReverseGeocoder];
//Create a new reverse geocoder to find the location for the given coordinate, and start the query
_reverseGeocoder = [[MKReverseGeocoder alloc] initWithCoordinate:_coordinate];
[_reverseGeocoder setDelegate:self];
[_reverseGeocoder start];
}
#pragma mark
#pragma mark -
#pragma mark MKAnnotation Delegate Procedure Implementations
- (NSString *)subtitle
{
NSString* subtitle = nil;
if (_placemark)
{
subtitle = [NSString stringWithString:[[_placemark.addressDictionary objectForKey:@"FormattedAddressLines"] objectAtIndex:1]];
}
else
{
subtitle = [NSString stringWithFormat:@"%lf, %lf", _coordinate.latitude, _coordinate.longitude];
}
return subtitle;
}
#pragma mark -
#pragma mark MKReverseGeocoderDelegate methods
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)newPlacemark {
if(geocoder != _reverseGeocoder)
{
NSLog(@"WARNING:::: MORE THAN ONE REVERSE GEOCODER!!!");
NSLog(@"_reverseGeocoder = %@",[_reverseGeocoder description]);
NSLog(@"geocoder = %@",[geocoder description]);
}
[self notifyCalloutInfo:newPlacemark];
[self resetReverseGeocoder];
}
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error {
if(geocoder != _reverseGeocoder)
{
NSLog(@"WARNING:::: MORE THAN ONE REVERSE GEOCODER!!!");
NSLog(@"_reverseGeocoder = %@",[_reverseGeocoder description]);
NSLog(@"geocoder = %@",[geocoder description]);
}
[self notifyCalloutInfo:nil];
[self resetReverseGeocoder];
}
#pragma mark -
#pragma mark Memory Management
- (void)dealloc {
[self resetReverseGeocoder];
[_title release], _title = nil;
[_placemark release], _placemark = nil;
[super dealloc];
}
@end
Second, I have created a Custom Annotation View
CustomAnnotationView.h
@interface CustomAnnotationView : MKAnnotationView {
}
@end
CustomAnnotationView.m
@implementation CustomAnnotationView
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
UIGraphicsBeginImageContext(CGSizeMake(30,30));
//Note: [UIImage drawInRect:radius:contentMode:] is a Three20 Procedure
[[UIImage imageNamed:@"annoationIcon.png"] drawInRect:CGRectMake(0,0,30,30) radius:6.0f contentMode:UIViewContentModeScaleAspectFill];
UIImage *iconImage = UIGraphicsGetImageFromCurrentImageContext();
//pop the context to get back to the default
UIGraphicsEndImageContext();
UIImageView *leftIconView = [[UIImageView alloc] initWithImage:iconImage];
self.leftCalloutAccessoryView = leftIconView;
[leftIconView release];
[iconImage release];
return self;
}
@end
And, Last But Not Least, the relevant parts of My New Object View Controller:
NewObjectViewController.m
@implementation NewObjectViewController
@synthesize managedObjectContext;
@synthesize object;
@synthesize objectMapView;
#pragma mark
#pragma mark -
#pragma mark Initialization
-(id) initWithManagedObjectContext:(NSManagedObjectContext*)context{
self = [super init];
if (self != nil) {
[self setManagedObjectContext:context];
Object *newObject = [NSEntityDescription insertNewObjectForEntityForName:@"Object" inManagedObjectContext:self.managedObjectContext];
self.object = [newObject retain];
}
return self;
}
#pragma mark
#pragma mark -
#pragma mark Memory Management
-(void) dealloc {
[object release], object=nil
objectMapView.delegate = nil;
[objectMapView release], objectMapView = nil;
[super dealloc];
}
#pragma mark
#pragma mark -
#pragma mark Enable/Disable Button and Textfield States
-(IBAction) updateSaveButtonState{
if([objectNameTextField.text isEmptyOrWhitespace])
{
[saveButton setEnabled:NO];
}
else {
[saveButton setEnabled:YES];
}
}
#pragma mark
#pragma mark -
#pragma mark UITextField Delegate - Optional Method Implementations
- (void)textFieldDidEndEditing:(UITextField *)textField{
[textField resignFirstResponder];
if([textField isEqual:objectNameTextField])
{
if(![objectNameTextField.text isEmptyOrWhitespace])
{
[object setName:objectNameTextField.text];
}
else {
[object setName:nil];
}
}
}
- (void)textFieldDidChange:(NSNotification*)aNotification{
[self updateSaveButtonState];
}
#pragma mark
#pragma mark -
#pragma mark Core Data Persistance Management Procedures
- (IBAction)save {
if([objectMapView annotations].count != 0)
{
NSLog(@"Cleaning up annotations");
[objectMapView removeAnnotations:[objectMapView annotations]];
}
NSError *error = nil;
//Double Check that all Object Information is Updated Before Saving
[self textFieldDidEndEditing:NameTextField];
if (![managedObjectContext save:&error]) {
// Handle error
exit(-1); // Fail
}
objectMapView.delegate = nil;
[self.delegate newObjectViewController:self didAddObject:object];
}
- (IBAction)cancel {
[managedObjectContext deleteObject:object];
if([objectMapView annotations].count != 0)
{
NSLog(@"Trying to clean up %d annotations",[objectMapView annotations].count);
[objectMapView removeAnnotations:[objectMapView annotations]];
}
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// Handle error
exit(-1); // Fail
}
objectMapView.delegate = nil;
[self.delegate newObjectViewController:self didAddObject:nil];
}
#pragma mark
#pragma mark -
#pragma mark Location Notification Observer Management
-(void) addLocationObserversAndStartUpdatingLocation{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(locationDeniedNotification) name:@"LOCATION_DENIED_NOTIFICATION" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(newLocationNotification) name:@"NEW_LOCATION_NOTIFICATION" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(locationErrorNotification) name:@"LOCATION_ERROR_NOTIFICATION" object:nil];
[[MyCLController sharedInstance].locationManager startUpdatingLocation];
}
-(void) removeLocationObserversAndStopUpdatingLocation{
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"LOCATION_DENIED_NOTIFICATION" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"NEW_LOCATION_NOTIFICATION" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"LOCATION_ERROR_NOTIFICATION" object:nil];
[[MyCLController sharedInstance].locationManager stopUpdatingLocation];
}
#pragma mark
#pragma mark -
#pragma mark Location Notification Callback Procedures
-(void) newLocationNotification{
if(([[MyCLController sharedInstance] locationManager].location != NULL) && ([[MyCLController sharedInstance] locationManager].location != nil))
{
[self removeLocationObserversAndStopUpdatingLocation];
// Add annotation to map
CustomMapAnnotation *annotation = [[CustomMapAnnotation alloc] initWithCoordinate:[[MyCLController sharedInstance] locationManager].location.coordinate title:@"Mark Location With Pin"];
[self.objectMapView addAnnotation:annotation];
[self.objectMapView selectAnnotation:annotation animated:YES];
[annotation release];
}
}
-(void) locationErrorNotification{
[self removeLocationObserversAndStopUpdatingLocation];
}
-(void) locationDeniedNotification{
[self locationErrorNotification];
}
#pragma mark
#pragma mark -
#pragma mark Location Update Procedures
-(IBAction) updateLocationWithPlacemark:(MKPlacemark*)placemark{
NSDictionary *addressDictionary = [[placemark addressDictionary] retain];
CLLocationCoordinate2D coordinate = [placemark coordinate];
//Update Latitude
[(GeoTag*)[(Location*)[object location] geoTag] setLatitude:[NSNumber numberWithDouble:coordinate.latitude]];
//Update Longitude
[(GeoTag*)[(Location*)[object location] geoTag] setLongitude:[NSNumber numberWithDouble:coordinate.longitude]];
//Update Country
[object setCountry:[addressDictionary objectForKey:@"CountryCode"]];
//Update City
[object setCity:[addressDictionary objectForKey:@"City"]];
//Update State/Province Initials
[object setStateOrProvince:[addressDictionary objectForKey:@"State"]];
//Update ZipCode/PostalCode
[object setZipCodeOrPostalCode:[addressDictionary objectForKey:@"ZIP"]];
[addressDictionary release];
}
#pragma mark
#pragma mark -
#pragma mark Notification Observers
-(void) addObservers{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldDidChange:) name:@"UITextFieldTextDidChangeNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(annotationCalloutInfoDidChange:) name:@"MKAnnotationCalloutInfoDidChangeNotification" object:nil];
}
-(void) removeObservers{
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"UITextFieldTextDidChangeNotification" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"MKAnnotationCalloutInfoDidChangeNotification" object:nil];
}
#pragma mark
#pragma mark -
#pragma mark Custom Annotation Update Notification Handler
-(void) annotationCalloutInfoDidChange:(NSNotification*)aNotification
{
CustomAnnotation *annotation = (CustomAnnotation*)[aNotification object];
[self updateLocationWithPlacemark:[annotation placemark]];
[self updateSaveButtonState];
}
#pragma mark
#pragma mark -
#pragma mark MKMapViewDelegate Optional Implementations
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if (annotation == mapView.userLocation) {
return nil;
}
CustomAnnotationView *annotationView = (CustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"CustomAnnotation"];
if (annotationView == nil) {
annotationView = [[[CustomAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"CustomAnnotation"] autorelease];
}
return annotationView;
}
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{}
#pragma mark
#pragma mark -
#pragma mark View LifeCycle
-(void) viewDidLoad{
[super viewDidLoad];
[self addObservers];
[self addLocationObserversAndStartUpdatingLocation];
}
@end
Additionally, here is the crash log:
Thread 0 Crashed:
0 libobjc.A.dylib 0x00003ec0 objc_msgSend + 24
1 UIKit 0x0005c8b0 -[UIImageView stopAnimating] + 76
2 UIKit 0x0005c808 -[UIImageView dealloc] + 20
3 CoreFoundation 0x0003963a -[NSObject release] + 28
4 MapKit 0x0006b8a4 -[MKAnnotationView dealloc] + 80
5 CoreFoundation 0x0003963a -[NSObject release] + 28
6 UIKit 0x0000ab2c -[UIView(Hierarchy) removeFromSuperview] + 592
7 UIKit 0x0005ca1c -[UIView dealloc] + 232
8 MapKit 0x0003a814 -[MKOverlayView dealloc] + 804
9 CoreFoundation 0x0003963a -[NSObject release] + 28
10 UIKit 0x0000ab2c -[UIView(Hierarchy) removeFromSuperview] + 592
11 UIKit 0x0005ca1c -[UIView dealloc] + 232
12 UIKit 0x000cb870 -[UIScrollView dealloc] + 284
13 MapKit 0x000699bc -[MKScrollView dealloc] + 88
14 CoreFoundation 0x0003963a -[NSObject release] + 28
15 UIKit 0x0000ab2c -[UIView(Hierarchy) removeFromSuperview] + 592
16 UIKit 0x000cb4a0 -[UIScrollView removeFromSuperview] + 68
17 UIKit 0x0005ca1c -[UIView dealloc] + 232
18 CoreFoundation 0x0003963a -[NSObject release] + 28
19 UIKit 0x0000ab2c -[UIView(Hierarchy) removeFromSuperview] + 592
20 UIKit 0x0005ca1c -[UIView dealloc] + 232
21 MapKit 0x00017794 -[MKMapView dealloc] + 1384
22 CoreFoundation 0x0003963a -[NSObject release] + 28
23 UIKit 0x0000ab2c -[UIView(Hierarchy) removeFromSuperview] + 592
24 UIKit 0x0005ca1c -[UIView dealloc] + 232
25 CoreFoundation 0x0003963a -[NSObject release] + 28
26 UIKit 0x0000ab2c -[UIView(Hierarchy) removeFromSuperview] + 592
27 UIKit 0x0005ca1c -[UIView dealloc] + 232
28 CoreFoundation 0x0003963a -[NSObject release] + 28
29 UIKit 0x0000ab2c -[UIView(Hierarchy) removeFromSuperview] + 592
30 UIKit 0x0005ca1c -[UIView dealloc] + 232
31 CoreFoundation 0x0003963a -[NSObject release] + 28
32 UIKit 0x0000ab2c -[UIView(Hierarchy) removeFromSuperview] + 592
33 UIKit 0x0005ca1c -[UIView dealloc] + 232
34 CoreFoundation 0x0003963a -[NSObject release] + 28
35 Foundation 0x00047990 NSPopAutoreleasePool + 238
36 QuartzCore 0x0001e0fc run_animation_callbacks(double, void*) + 600
37 QuartzCore 0x0001de64 CA::timer_callback(__CFRunLoopTimer*, void*) + 156
38 CoreFoundation 0x000574bc CFRunLoopRunSpecific + 2192
39 CoreFoundation 0x00056c18 CFRunLoopRunInMode + 44
40 GraphicsServices 0x0000436c GSEventRunModal + 188
41 UIKit 0x00003c28 -[UIApplication _run] + 552
42 UIKit 0x00002228 UIApplicationMain + 960
43 TestApp 0x0000244a main (main.m:14)
44 TestApp 0x000021d4 start + 44