:)
I have a really strange problem when trying to retrieve the properties of a selected annotation. Here is the short description of my problem:
I pass the first object of the selected annotations array to a new array as it's the only one I need (acc. to Apple doc, passing the selectedAnnotations array to a new array only selects the first object. But I did tried to pull the object directly from the selectedAnnotations array at index path 0 and it's the same problem).
Then I transform the object into a Custom annotation object (as this is what the object should be).
Afterwards I try to access the properties of my custom annotation temp object. Here is when everything breaks loose. NSLog of the object only shows memory address. Text property is null. So basically I can't access it.
I would appreciate any help on what am I doing wrong or what approach should I use.
Thank you kindly!
Here is the code:
-(void) mapView:(MKMapView *)aMapView didSelectAnnotationView:(MKAnnotationView *)view
{
if ([view isUserInteractionEnabled])
// NSLog(#"Tapped!!!");
{
NSArray* selectedAnnotation=mapView.selectedAnnotations;
CustomAnnotations *selectedAnn=[selectedAnnotation objectAtIndex:0];
NSLog(#"selected annotation text is %#", selectedAnn.text);
My custom annotation class has a coordinate and a text property and it's being placed on map with this code:
CustomAnnotations* commentAnnotation = [[[CustomAnnotations alloc] initWithLocation:mapView.userLocation.location.coordinate andTitle:#"comment" andText:text]autorelease];
[mapView addAnnotation:commentAnnotation];
Furthermore, the view for annotation has the following coding:
-(MKAnnotationView *) mapView:(MKMapView *) aMapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKAnnotationView *customAnnotation = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pin"];
if(!customAnnotation)
{
customAnnotation = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pin"]autorelease];
}
customAnnotation.centerOffset=CGPointMake(10, -30);
if ([annotation title]==#"comment")
{
customAnnotation.userInteractionEnabled=YES;
customAnnotation.image=[UIImage imageNamed:#"NewCommentsPin.png"];
}
return customAnnotation;
}
Any help would be much appreciated!
I figured out the problem: My custom annotation class was releasing the text in dealloc.
I still have a long way to go until I understand when to release and when not to but one step at a time!:)
Here is your release cheat sheet.
When you create an object, and it has any of the words new, alloc,
copy or retain in the constructor then you own it and you have
to release it if you do not need the reference any more.
Related
I'm in a situation where in the
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
method, I need to dynamically change the implementation of the subtitle method for the annotation object. The reason I need to do this is because I'm doing some computations based on latitudes and longitudes that are changing frequently (which I wish to display as the subtitle) ...so when I first create the id object, it doesn't make sense to do that computation at that time.
How would I dynamically override the subtitle method for my custom id object? Can someone point me in the direction of doing that? Or are there any other approaches I could take?
EDIT:
To be a bit more clear... I want to add the annotation custom object to the map BEFORE figuring out what the title and subtitle should be for that annotation object. I want to wait until the user touches on the annotation on the map..and when it shows the popup, that's where I want to calculate what to show as the subtitle. That's why I thought of dynamically overriding the subtitle method of the custom id object.
Thanks!
If you need to dynamically change the implementation of a method at run time, that might call for an application of strategy pattern.
With C blocks, we can do it in a flexible and quick way. Have your custom annotation delegate its implementation of subtitle to the return value of a block property. Then, in your map view's delegate, define blocks that calculate the subtitle based on your requirements, and assign them to the annotation's property.
Sketch of a custom annotation implementation that delegates its subtitle implementation:
typedef NSString* (^AnnotationImplementationSubtitleBlock)();
#interface AnnotationImplementation : NSObject <MKAnnotation>
#property (nonatomic, copy) AnnotationImplementationSubtitleBlock *subtitleBlock;
#end
#implementation AnnotationImplementation
- (NSString *)subtitle
{
return self.subtitleBlock();
}
// Rest of MKAnnotation methods
#end
Also a sketch of the implementation of the map view delegate method where the blocks are created and assigned:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
AnnotationImplementation *customAnnotation = (AnnotationImplementation *)annotation;
if (/* some condition causing you to do it one way */) {
customAnnotation.subtitleBlock = ^{
//calculate the subtitle some way
return calculatedSubtitle;
}
}
else if (/* some condition causing you to do it another way */) {
customAnnotation.subtitleBlock = ^{
//calculate the subtitle the other way
return calculatedSubtitle;
}
}
... rest of method
}
I'm trying to replace the blue 'currentLocation' dot with a custom annotation. That code is working well (I am just implementing viewForAnnotation and cycling through that, replacing the annotation ofClass MKUserLocation with a custom image).
However, once I replace the annotation, the user's current location stops updating. All related functions (like didUpdateUserLocation) thus stop being called. This causes a lot of problems. I tried implementing the various code blocks at MKUserLocation Custom View not moving! but I couldn't get it to work. After extensive Googling and stackoverflow searching, I've come up with nothing.
Does anyone have a solution to this problem?
Here is the code for the viewForAnnotation method:
_userDot is an instance variable within MapScreen.m (the place where all of this code is). It's alloced in the viewDidLoad and is of type MKAnnotationView. Basically I couldn't just get rid of the annotation, so I wanted to set it to an invisible image instead (currently 1x1 for debugging).
- (MKAnnotationView *)mapView:(MKMapView *)newMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]) {
//Initialize a new MKAnnotationView using the current userLocation annotation
//Then make it invisible (because we are cheap like that)
//TODO: Make it actually invisible (right now it is 1x1 for debugging)
if (_userDot)
{
_userDot.annotation = annotation;
_userDot.image = [UIImage imageNamed:#"mister_taco"];
_userDot.frame = CGRectMake(1, 1, 1, 1);
return _userDot;
}
else
return nil;
}
else {*/
return nil;
//}
}
Thanks,
Brian
Is there a method (or any way) you can get the "index path" of the annotation inside the method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation
What I want to do is get something inside an array according to the annotation. One of the methods I think that I want to copy is - tableView:cellForRowAtIndexPath: and use the "indexPath".
Searched a lot but not found any result. Or maybe I'm just bad at searching. Any reference will be greatly appreciated.
NSUInteger index = [mapView.annotations indexOfObject:annotation];
Since the underlying array is a mutable one, note that the index can change. This seems to be the closest indexPath like information you can get.
To answer my own question (this how it works for me):
I created an instance in the custom annotation and transferred the data there, then inside - mapView:viewForAnnotation: (I used custom pinView) I imported and created an instance of my CustomAnnotation and got the data from there.
[customAnnotation setSomething: data];
- mapView:viewForAnnotation:
{
pinView = [[[CustomPinView alloc] initWithAnnotation: annotation reuseIdentifier: PinIdentifier] autorelease];
}
- initWithAnnotation:reuseIdentifier:
{
CustomAnnotation *customAnnotation = (CustomAnnotation *)annotation;
[customAnnotation something];
}
Overkill for me but it gets the job done.
I've subclassed MKAnnotation so that i can assign objects to each annotation, this is so that when the rightCalloutAccessoryView is clicked i can push a navigation controller with the object passed to it and display the objects data in another view.
This all works great apart from one thing, i've extended upon initWithCoordinate like so:
-(id)initWithCoordinate:(CLLocationCoordinate2D)coord andObject:(NSManagedObject *)object {
[self setPlace:object];
coordinate = coord;
title = [place valueForKey:#"name"];
subtitle = [place valueForKey:#"address"];
return self;
}
Although everything is working great i'm recieving the warning:
NO '-initWithCoordinate:andObject:' method found
Which means i'm doing something wrong somewhere, what is the correct way to go about adding upon initWithCoorinate?
Put the prototype -(id)initWithCoordinate:(CLLocationCoordinate2D)coord andObject:(NSManagedObject *)object in .h file.
Ok, so I’m having this problem. What I want to do is manually add multiple annotations to a map. When I add just one annotation, it works flawlessly. The pin drops, you can click on it to see its callout, life is good.
The problem comes when I want to add more than one. When I add the second, suddenly the pin’s aren’t coloured correctly (i.e. depending on their magnitude they should be a certain color, but they’re now both the same…), and more importantly when you click on them, to see their callout, the app crashes with exex_bad_access. I really have no idea what’s wrong, maybe I’m adding too many views to the map? But it’s only 9 pins and the pins themselves add just fine.
Here’s my code…
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *stops = [[NSMutableArray alloc] init]; //Get list of all the stops available
Bus *bus1 = [[Bus alloc] init]; // Bus 1 holds the stops
stops = [bus1 returnStops];
for (NSString *stop in stops) //Go through each stop to add annotation to map
{
Bus *bus2 = [bus1 initWithStop:stop]; //Create an instance of bus with a given stop
MapAnnotation *eqAnn = [MapAnnotation annotationWithBus:bus2];
[self.mapView addAnnotation:eqAnn]; //Add the annotation to the map
//[eqAnn release];
//[bus2 release];
}
[self recenterMap];
[stops release];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *view = nil;
if(annotation != mapView.userLocation) {
MapAnnotation *eqAnn = (MapAnnotation*)annotation;
view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:#"busLoc"];
if(nil == view) {
view = [[[MKPinAnnotationView alloc] initWithAnnotation:eqAnn
reuseIdentifier:#"busLoc"] autorelease];
}
CGFloat magnituide = [eqAnn.bus.magnitude floatValue];
if(magnituide >= .80f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorRed];
} else if(magnituide >= .60f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorPurple];
} else
{
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorGreen];
}
[(MKPinAnnotationView *)view setAnimatesDrop:YES];
[view setCanShowCallout:YES];
}
return view;
}
even tried removing the second function, but it didn’t do anything.
Thanks for the help!
P.S I should also add, there’s usually one or two pins out of the 9 which works when you click the annotation…
If i even try to manually just two annotations by hand in the program (i.e., remove the loop), it still fails and the color is still wrong...
It would appear that your memory management of the stops variable is incorrect. You allocate a mutable array, then replace that array with the return value of -[Bus returnStops], then release that. Also it's not clear what's going on with bus2 - does -[Bus initWithStop:] return a different instance of Bus? It's not usual to send any method -init* on an already-initialised object. I think that you probably are confused by the memory management conventions in Cocoa Touch. Here's a collection of articles and other references on Cocoa memory management (which is the same beast).
Have you tried using AddAnnotations instead of add annotation? - (void)addAnnotations:(NSArray *)annotations. This might work for you...but looking at the answer above and further inspection you are having some memory managment issues in your viewDidLoad (though thi s might not be the cause of your problem, but it could be). First of you are allocating the array (stops) and then ovveriding it with some array in the Bus object, this will cause a leak. Also you are then releasing that array which might be causing the crash since you are releasing the array that is actually in the Bus object w ithout having increased a reference count to it. I am not sure what initWithStop is doing but you might be getting a leak here too if initWithStop retains the object.
I wouldn't call it a memory management problem -- I'd just say you are using array references incorrectly.
After constructing the array with NSMutableArray *stops = [[NSMutableArray alloc] init], the next step is to use [stops addObject: ] to add each stop you want to store.
After that? It's not clear what you are really trying to do.
SO the answer was that I kept sending bus1 the init object, so it got confused.
"Hi David,
Your data model looks hosed to me. You only have one bus object that you are repeatedly sending initWithStop: to.
Hope this helps.
Good luck!
"
Thank you guys for your help! You all helped me quite a bit!