MKMapKit - EXC_BAD_ACCESS when looping through MKAnnotations - objective-c

I've been stuck on this EXC_BAD_ACCESS error 2 days now. I have a reloadAnnotations method that removes all annotations before adding new annotations. Before removing the annotation this method should be checking to see if the new set contains the same location so it's not removed and re-added. But as soon as I try to trace out the current annotation title I get this error Thread 1: Program received signal: "EXC_BAD_ACCESS"
And when I view the annotation in the debugger the title property says "Invalid Summary". It must be caused by a value not being retained but I've tried everything and can't figure it out.
Why can't I log the annotation title to NSLog?
And why can't I compare each title and coords to other objects?
BrowseController.m
-(void)reloadAnnotations
{
NSMutableArray *toRemove = [NSMutableArray arrayWithCapacity:10];
for (id annotation in _mapView.annotations) {
if (annotation != _mapView.userLocation) {
//ParkAnnotation *pa = (ParkAnnotation *)annotation;
ParkAnnotation *pa = annotation;
NSLog(#"pa.title %#", pa.title); // Thread 1: Program received signal: "EXC_BAD_ACCESS"
[toRemove addObject:annotation];
}
}
// DON'T REMOVE IT IF IT'S ALREADY ON THE MAP!!!!!!
for(RKLocation *loc in locations)
{
CLLocationCoordinate2D location;
location.latitude = (double)[loc.lat doubleValue];
location.longitude = (double)[loc.lng doubleValue];
ParkAnnotation *parkAnnotation = [[ParkAnnotation alloc] initWithTitle:loc.name andCoordinate:location];
[_mapView addAnnotation:parkAnnotation];
}
[_mapView removeAnnotations:toRemove];
}
- (MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation
{
NSLog(#"BrowseViewController map viewForAnnotation");
MKPinAnnotationView *pin = (MKPinAnnotationView *)[_mapView dequeueReusableAnnotationViewWithIdentifier: #"anIdentifier"];
if (pin == nil){
pin = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier: #"anIdentifier"] autorelease];
pin.pinColor = MKPinAnnotationColorRed;
pin.animatesDrop = YES;
pin.canShowCallout = YES;
}
else{
pin.annotation = annotation;
}
return pin;
}
ParkAnnotation.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface ParkAnnotation : NSObject <MKAnnotation> {
NSString *title;
CLLocationCoordinate2D coordinate;
}
#property (nonatomic, copy) NSString *title;
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
- (id)initWithTitle:(NSString *)ttl andCoordinate:(CLLocationCoordinate2D)c2d;
#end
ParkAnnotation.m (edited: see Wolfgangs comments below )
#import "ParkAnnotation.h"
#implementation ParkAnnotation
#synthesize title, coordinate;
- (id)initWithTitle:(NSString *)ttl andCoordinate:(CLLocationCoordinate2D)c2d {
self = [super init];
if (self) {
title = ttl;
coordinate = c2d;
}
return self;
}
- (void)dealloc {
[title release];
[super dealloc];
}
#end

Although you have declared title has a copy type property, it never is copied as you don't use the setter method and directly assigned. You are even releasing it without ownership. Change it like this,
title = [ttl copy];

The initializer in ParkAnnotation.m isn't written following ObjC conventions. The self variable is never set, the designated initializer of a class should follow the following pattern:
- (id)init
{
self = [super init];
if (self)
{
/* custom initialization here ... */
}
return self;
}
Since self is not set, the accessor methods used in the caller will fail; the container object (inside ParkAnnotation.m referenced with self) will be nil or some bogus value when trying to access a property inside the object from another class.

Related

Custom annotation with custom variable

I currently have a map view controller in which i add annotations to based on some parsed json data.
I'm trying to pass a value from this json data to the following segue and they way i want to do this is to add a custom variable to each annotation (venueId) so when it is pressed i can set a global value that the next segue gets via some logic.
However everything i have tried has resulted in a NUll value for the venueId, my code is as follows:
MyLocation.H
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface MyLocation : NSObject <MKAnnotation>{
NSNumber *venueId;
}
#property (nonatomic,readonly) NSNumber *venueId;
- (id)initWithName:(NSString *)name address:(NSString *)address coordinate:(CLLocationCoordinate2D)coordinate venueId:(NSNumber*)venueId;
#end
MyLocation.M
#import "MyLocation.h"
#import "GlobalData.h"
#interface MyLocation ()
#property (nonatomic,copy) NSString *name;
#property (nonatomic,copy) NSString *address;
#property (nonatomic,assign) CLLocationCoordinate2D coordinate;
#end
#implementation MyLocation
#synthesize venueId = _venueId;
- (id) initWithName:(NSString *)name address:(NSString *)address coordinate:(CLLocationCoordinate2D)coordinate venueId:(NSNumber*)venueId{
if ((self = [super init])) {
self.name = name;
self.address = address;
self.coordinate = coordinate;
_venueId = self.venueId;
}
return self;
}
- (NSString *)title{
return _name;
}
- (NSString *)subtitle{
return _address;
}
- (CLLocationCoordinate2D)coordinate{
return _coordinate;
}
- (NSNumber *)venueId{
return _venueId;
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
NSLog(#"THE CALL OUT has been pressed");
}
#end
PLotting the venues in my MapViewController.M
// plot venues based on data passed in
- (void)plotVenuePositions{
for (id<MKAnnotation> annotation in _mapView.annotations){
// start fresh and remove any annotations in the view
[_mapView removeAnnotation:annotation];
}
for (NSDictionary *row in [[GlobalData sharedGlobalData]venuesArray]){
NSNumber *venueId = [row objectForKey:#"v_id"];
NSString *venueName =[row objectForKey:#"v_name"];
NSNumber *venueLat = [row objectForKey:#"v_lat"];
NSNumber *venueLon = [row objectForKey:#"v_lon"];
NSString *venueTown = [row objectForKey:#"t_name"];
// create co-ord
CLLocationCoordinate2D coordinate;
// set values
coordinate.latitude = venueLat.doubleValue;
coordinate.longitude = venueLon.doubleValue;
// create annotation instance
MyLocation *annotation = [[MyLocation alloc]initWithName:venueName address:venueTown coordinate:coordinate venueId:venueId];
// add annotation
[_mapView addAnnotation:annotation];
NSLog(#"VNEU ID IS %#",venueId);
NSLog(#"ANNOTATION name is %#", annotation.title);
NSLog(#"ANNOTATION subtitle is %#", annotation.subtitle);
NSLog(#"ANNOTATION description is %#", annotation.description);
NSLog(#"ANNOTATION venue ID IS %#", (MyLocation *)annotation.venueId);
}
}
And finally the annotation checks in MapViewController.M
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[MyLocation class]]) {
MKAnnotationView *annotationView = (MKAnnotationView *) [_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.image = [UIImage imageNamed:#"pin_orange.png"];
// set the cell to have a callout button
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
else{
annotationView.annotation = annotation;
}
return annotationView;
}
return nil;
}
In the initWithName method, this line:
_venueId = self.venueId;
does not set the current location's venue id to the init method's parameter value venueId.
It sets it to the current location's property value for venueId.
Since that property value is backed by _venueId, the line is effectively setting it equal to itself and since it is initially null, it stays null.
The line should be:
_venueId = venueId;
or if you are not using ARC:
_venueId = [venueId retain];
This line will, however, give you a compiler warning that the "local variable hides the instance variable". This is because the method parameter name is the same as the property name.
Although the change will work, to remove the compiler warning, change the name of the method parameter to something other than venueId (eg. iVenueId) and then the changed line would be:
_venueId = iVenueId;

Message Sent To Deallocated Instance... Sent During #synthesize?

I've been using code from Raphael Cruzeiro's PDF Annotator, and have discovered a number of memory leaks (ARC is off, and will stay off to support older devices). After patching most of them up, I'm down to the last couple, and they have me stumped. So in a class called PDFDocument, he has properties for a CGPDFPageRef, CGPDFDocument, and a custom annotation class #synthesize'd. I had to pepper his dealloc method with releases and eliminate some dangling pointers, which works well except for one small problem: After about 3 complete retain-release cycles, it crashes at the #synthesize line for his annotation object... I've never seen a SIGABRT because of a deallocated object sent during #synthesize, so naturally have no idea how to fix it. If I remove the release code in dealloc, it leaks, but if I leave it in, it crashes. Here's the code for the PDFDocument class:
//.h
#import <Foundation/Foundation.h>
#class Annotation;
#interface PDFDocument : NSObject {
Annotation *_annotation;
}
- (id)initWithDocument:(NSString *)documentPath;
- (NSInteger) pageCount;
- (void) loadPage:(NSInteger)number;
- (BOOL)save;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSString *hash;
#property (readwrite, nonatomic, assign) CGPDFDocumentRef document;
#property (readwrite, nonatomic, assign) CGPDFPageRef page;
#property (nonatomic, retain) NSString *version;
#property (nonatomic, assign) BOOL dirty;
#property (nonatomic, retain) Annotation *annotation;
#end
//.m
#import "PDFDocument.h"
#import "Annotation.h"
#import "HashExtensions.h"
#import "DocumentDeserializer.h"
#import "DocumentSerializer.h"
#implementation PDFDocument
#synthesize document;
#synthesize page;
#synthesize annotation = _annotation; //after 3rd cycle, it crashes here.
#synthesize name;
#synthesize hash;
#synthesize dirty;
#synthesize version;
- (id)initWithDocument:(NSString *)documentPath
{
if((self = [super init]) != NULL) {
self.name = [documentPath lastPathComponent];
if ([self.name isEqualToString:#"Musette.pdf"] || [self.name isEqualToString:#"Minore.pdf"] || [self.name isEqualToString:#"Cantata.pdf"] || [self.name isEqualToString:#"Finalé.pdf"])
{
CFURLRef ref = CFBundleCopyResourceURL(CFBundleGetMainBundle(), (CFStringRef)self.name, NULL, NULL);
self.document = CGPDFDocumentCreateWithURL(ref);
self.page = CGPDFDocumentGetPage(document, 1);
self.version = #"1.0";
DocumentDeserializer *deserializer = [[[DocumentDeserializer alloc] init] autorelease];
self.annotation = [deserializer readAnnotation:[[(NSURL*)ref absoluteString] stringByDeletingPathExtension]];
CFRelease(ref);
}
else {
CFURLRef pdfURL = (CFURLRef)[[NSURL alloc] initFileURLWithPath:documentPath];
self.document = CGPDFDocumentCreateWithURL(pdfURL);
self.page = CGPDFDocumentGetPage(document, 1);
self.version = #"1.0";
DocumentDeserializer *deserializer = [[[DocumentDeserializer alloc] init] autorelease];
self.annotation = [deserializer readAnnotation:[[(NSURL*)pdfURL absoluteString] stringByDeletingPathExtension]];
CFRelease(pdfURL);
CGPDFPageRelease(self.page);
}
}
return self;
}
- (NSInteger)pageCount
{
return CGPDFDocumentGetNumberOfPages(self.document);
}
- (void)loadPage:(NSInteger)number
{
self.page = CGPDFDocumentGetPage(document, number);
}
- (BOOL)save
{
DocumentSerializer *serializer = [[[DocumentSerializer alloc] init] autorelease];
[serializer serialize:self];
self.dirty = NO;
return !self.dirty;
}
- (void)dealloc
{
CGPDFDocumentRelease(self.document);
if (self.annotation != nil && _annotation != nil) {
[_annotation release];
self.annotation = nil;
} //my attempt to prevent the object from being over-released
self.document = nil;
self.name = nil;
[super dealloc];
}
#end
Then I ran it through Instruments to find zombie objects, and sure enough, Instruments found a deallocated object being sent a message at the exact same #synthesize line!
Does anyone have any idea what's going on and how to fix it?
This bit looks very wrong:
if (self.annotation != nil && _annotation != nil) {
[_annotation release];
self.annotation = nil;
}
Firstly, why are you checking self.annotation and _annotation for nil-ness. That's effectively doing the same check twice.
Secondly, you're using direct ivar access to release _annotation and then the setter for annotation will be releasing _annotation again and setting _annotation = nil. Effectively it's doing this:
if (self.annotation != nil && _annotation != nil) {
[_annotation release];
[_annotation release];
_annotation = [nil retain];
}
Which as you can see, is going to over-release _annotation.
Also, seriously, just use ARC. ARC is (mainly) compile time and has nothing to do with the device or OS version it's running on. The only bit that's not supported on pre iOS 5 is auto nil-ed weak pointers. But that really shouldn't be a problem as that's totally new in Lion / iOS 5 anyway.

How to release property of static class

i have a static class witch has two property,like below ...
#interface Global : NSObject
{
BarcodeScanner* scanner;
NSInteger warehouseID;
}
#property(assign) BarcodeScanner* scanner;
#property(assign) NSInteger warehouseID;
+(Global *)sharedInstance;
#end
#import "Global.h"
#implementation Global
#synthesize scanner,warehouseID;
+ (Global *)sharedInstance
{
static Global *globalInstance = nil;
if (nil == globalInstance) {
globalInstance = [[Global alloc] init];
globalInstance.scanner = [[BarcodeScanner alloc] init];
globalInstance.warehouseID = 1;
}
return globalInstance;
}
-(void) dealloc
{
[super dealloc];
}
#end
now when i analyze project in Xcode i got warning for memory leak for scanner and warehouseID properties , and when i try to release them in dealloc method like ...
[[[Global sharedInstance] scanner]release];
i got warning "incorrect decrement of the reference count of an object..."
how should i resolve this problem.
so thanks for any help.
The warning is because your code does not match the rules Analyzer uses. To avoid the warning
make the scanner property retain
change the the instantiation or BarcodeScanner to be autorelease
add a release for scanner in dealloc
Example (reformatted just to save space):
#class BarcodeScanner;
#interface Global : NSObject {
BarcodeScanner* scanner;
NSInteger warehouseID;
}
#property(retain) BarcodeScanner* scanner;
#property(assign) NSInteger warehouseID;
+(Global *)sharedInstance;
#end
#implementation Global
#synthesize scanner,warehouseID;
+ (Global *)sharedInstance {
static Global *globalInstance = nil;
if (nil == globalInstance) {
globalInstance = [[Global alloc] init];
globalInstance.scanner = [[[BarcodeScanner alloc] init] autorelease];
globalInstance.warehouseID = 1;
}
return globalInstance;
}
-(void) dealloc {
[scanner release];
[super dealloc];
}
#end
just leave it to autorelease pool
globalInstance.scanner = [[[BarcodeScanner alloc] init] autorelease];

MKAnnotationView update title and subtitle with distance when event is received

I'm developing an iphone app using mapkit and CLLocationManager.
I put lots of MKPinAnnotationView on map (about 100) and I want to update all callout's subtitle whith user distance when I receive it.
How to do it ?
Thanks
I try this to update subtitle callout with new location but it didn't work well.
In my MyAppDelegate.h
extern NSString * const GMAP_USERLOCATION_CHANGED;
#interface MyAppDelegate : NSObject <UIApplicationDelegate> {
CLLocationManager *locationManager;
CLLocation *userLocation;
}
#property (nonatomic, retain) CLLocationManager *locationManager;
#property (nonatomic, retain) CLLocation *userLocation;
#end
In my MyAppDelegate.m
#implementation MyAppDelegate
NSString * const GMAP_USERLOCATION_CHANGED = #"gMapUserLocationChanged";
#synthesize locationManager, userLocation;
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
userLocation = nil;
[[self locationManager] startUpdatingLocation];
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
}
#pragma mark -
#pragma mark Core Location delegate
- (CLLocationManager *)locationManager
{
if (locationManager != nil)
{
return locationManager;
}
locationManager = [[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
locationManager.delegate = self;
return locationManager;
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
self.userLocation = newLocation;
// send notification to defaulcenter
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:GMAP_USERLOCATION_CHANGED object:self.userLocation];
}
- (void)dealloc {
[window release];
[locationManager release];
[userLocation release];
[super dealloc];
}
#end
I made a customAnnotationView named MyAnnotation.
in MyAnnotation.h :
#interface MyAnnotation : MKPinAnnotationView<MKAnnotation>
{
double longitude;
double latitude;
NSString *title;
NSString *subtitle;
}
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSString *subtitle;
#property double longitude;
#property double latitude;
#end
in MyAnnotation.m :
#import "MyAnnotation.h"
#import "MyAppDelegate.h"
#implementation MyAnnotation
#synthesize title, subtitle;
#synthesize longitude;
#synthesize latitude;
-(id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self != nil)
{
NSLog(#"add observer");
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(receivedNewUserLocation:) name:GMAP_USERLOCATION_CHANGED object:nil];
}
return self;
}
- (void)setTitleAndSubtitle
{
[self setTitleAndSubtitle:nil];
}
- (id)setTitleAndSubtitle:(CLLocation*)userLocation
{
CLLocationDistance dist = -1;
if(userLocation)
{
CLLocation *poiLoc = [[CLLocation alloc] initWithLatitude:self.latitude longitude:self.longitude];
dist = [userLocation distanceFromLocation:poiLoc] / 1000;
NSLog(#"distance is now %.f", dist);
}
title = #"the Title of the poi!";
subtitle = [NSString stringWithFormat:#"Distance: %#",
dist > -1 ? [NSString stringWithFormat:#"%.2f km", dist] : #"-"
];
return self;
}
- (void)receivedNewUserLocation:(NSNotification *)userLocationNotification
{
CLLocation *userlocation = (CLLocation*)[userLocationNotification object];
[self setTitleAndSubtitle:userlocation];
}
- (CLLocationCoordinate2D)coordinate;
{
CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = latitude;
theCoordinate.longitude = longitude;
return theCoordinate;
}
- (NSString *)title
{
return title;
}
- (NSString *)subtitle
{
return subtitle;
}
- (void)dealloc
{
[title release];
[subtitle release];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self];
[super dealloc];
}
#end
In the end I use it like that in my MapViewController (I put only the viewForAnnotation delegate method here):
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MyAnnotation *annotationView = nil;
MyAnnotation* myAnnotation = (MyAnnotation *)annotation;
// try to dequeue an existing pin view first
NSString* identifier = #"CustomMapAnnotation";
MyAnnotation *customPinView = (MyAnnotation *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(nil == customPinView)
{
// if an existing pin view was not available, create one
customPinView = [[[MyAnnotation alloc]
initWithAnnotation:myAnnotation
reuseIdentifier:identifier]
autorelease];
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
}
annotationView = customPinView;
[annotationView setEnabled:YES];
[annotationView setCanShowCallout:YES];
return annotationView;
}
After all of this subtitle is not update after the mkannotation is load on map....
What's wrong ?
thanks for your help...
When you update the title, you should notify the MKAnnotationView to update the callout view, by whichever KVO manner that fits your need best, e.g.:
synthesize or implement setter for title and use
self.title = #"new title";
use explicit KVO notifications
[self willChangeValueForKey:#"title"];
[title release];
title = [newTitle copy];
[self didChangeValueForKey:#"title"];
Ditto for subtitle.
It depends on how you are creating your MKAnnotations.
You should probably have an object like "Place" represented by Place.h and Place.m, which conform to the MKAnnotation protocol...
Place would have a property along the lines of
float distance;
Then your subtitle method (part of MKAnnotation) would do something like this
- (NSString *)subtitle
{
return [NSString stringWithFormat:#"%0.2f Miles Away", distance];
}
That subtitle method gets called by the mapview constantly (in fact its almost ridiculous how often it gets called), so as soon as you manipulate the value of distance, it will be reflected on the map (perhaps as early as the next time you tap on the pin).

Why "copy" is not being invoked?

I have the following object:
#interface SomeObject : NSObject
{
NSString *title;
}
#property (copy) NSString *title;
#end
And I have another object:
#interface AnotherObject : NSObject{
NSString *title;
}
#property (copy) NSString *title;
- (AnotherObject*)init;
- (void)dealloc;
- (void) initWithSomeObject: (SomeObject*) pSomeObject;
+ (AnotherObject*) AnotherObjectWithSomeObject (SomeObject*) pSomeObject;
#end
#implementation AnotherObject
#synthesize title
- (AnotherObject*)init {
if (self = [super init]) {
title = nil;
}
return self;
}
- (void)dealloc {
if (title) [title release];
[super dealloc];
}
-(void) initWithSomeObject: (SomeObject*) pSomeObject
{
title = [pSomeObject title]; //Here copy is not being invoked, have to use [ [pSomeObject title] copy]
}
+ (AnotherObject*) AnotherObjectWithSomeObject (SomeObject*) pSomeObject;
{
[pSomeObject retain];
AnotherObject *tempAnotherObject = [ [AnotherObject alloc] init];
[tempAnotherObject initWithSomeObject: pSomeObject];
[pSomeObject release];
return tempAnotherObject;
}
#end
I do not understand, why copy is not being invoked when I am assigning "title = [pSomeObject title]". I always thought if i set "copy" in property it is always going to be invoked. I have a error in my code or I don't understand something?
Thank you in advance.
For a setter to get called you need to use the dot notation.
self.title = [pSomeObject title];
or...to use the dot notation for pSomeObject too
self.title = pSomeObject.title;