Problems Removing Annotations from a MKMapView - objective-c

I am trying to remove all the annotations of a map. The class that contains the MKMapView implements the MKMapViewDelegate. I am calling the remove all annotations method when a button (displayed in a modal view) is clicked. I am using the next code to remove all the annotations.
- (void)removeAllAnnotations {
NSMutableArray *annotationsToRemove = [NSMutableArray arrayWithCapacity:[self.mapView.annotations count]];
for (int i = 0; i < [self.mapView.annotations count]; i++) {
if ([[self.mapView.annotations objectAtIndex:i] isKindOfClass:[AddressAnnotation class]]) {
[annotationsToRemove addObject:[self.mapView.annotations objectAtIndex:i]];
}
}
[self.mapView removeAnnotations:annotationsToRemove];
}
The code works right but after calling the method, and when I try to add new annotations to the empty map, the class does not call to the viewForAnnotations method and the annotations do not drop down and do not show a disclosure button into the annotation view. Why is this happening?
Thanks for reading.
Edited:
The annotations are shown but with out calling the view for annotation method (with out dropping down and with out including a disclosure button into the annotation view). Here is the method that I use to add the annotations and the viewForAnnotation method.
- (void)loadAnnotations:(NSString *)type {
NSString *path = [[NSBundle mainBundle] pathForResource:#"PlacesInformation" ofType:#"plist"];
NSMutableArray *tmpArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
for (int i = 0; i < tmpArray.count; i++) {
// Breaks the string down even further to latitude and longitude fields.
NSString *coordinateString = [[tmpArray objectAtIndex:i] objectForKey:#"coordinates"];
NSString *option = [[tmpArray objectAtIndex:i] objectForKey:#"type"];
if ([option isEqualToString:type]) {
CLLocationCoordinate2D currentCoordinate = [self stringToCoordinate:coordinateString];
AddressAnnotation *annotation = [[[AddressAnnotation alloc] initWithCoordinate:currentCoordinate] autorelease];
[self.mapView addAnnotation:annotation];
}
}
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation {
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
else { // Handles the other annotations.
// Try to dequeue an existing pin view first.
static NSString *AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView) {
// If an existing pin view was not available, creates one.
MKPinAnnotationView *customPinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
// Adds a detail disclosure button.
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
} else
pinView.annotation = annotation;
}
return nil;
}

In viewForAnnotation, in the case where you are reusing a view, you are currently returning nil.
This else part:
} else
pinView.annotation = annotation;
Should probably be:
} else {
pinView.annotation = annotation;
return pinView;
}

Related

passing variable from MKAnnotationView to segue

I have an MKMapView, that I drop a number of pins on from an array. Each map pin has an MKAnnotationView that links to a segue and another controller.
All of this is working perfectly now, but I can't see how to pass a variable through the MKAnnotationView to the segue.
I can work with either passing a string or an integer, as I could pass a username as a string, or pass the id of the array and use that in the second view controller after the segue.
My working code for looping through my array, dropping the pins, adding the annotations, and then calling the segue is as below.
This all works fine, populates the map, drops the bins, and the annotations work and link to the new segue. This is all perfect!
I just need to be able to pass a variable through from the map pin array to the second view controller segue.
- (void)loadMapPins {
for (int i=0; i<maparray.count; i++){
Location *item = _feedItems1[i];
CLLocationCoordinate2D myCoordinate = {[item.latitude doubleValue], [item.longitude doubleValue]};
MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
point.coordinate = myCoordinate;
point.title = item.firstname;
point.subtitle = [NSString stringWithFormat: #"%#", item.username];
[self.mapView addAnnotation:point];
}
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation {
if (annotation == self.mapView.userLocation) return nil;
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView* customPin = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
customPin.pinColor = MKPinAnnotationColorRed;
customPin.animatesDrop = YES;
customPin.canShowCallout = YES;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
customPin.rightCalloutAccessoryView = rightButton;
return customPin;
}
- (IBAction)showDetails:(id)sender {
[self performSegueWithIdentifier:#"showProfileFromMap" sender:self];
}
You should use this MKMapViewDelegate method
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
CustomAnnotationClass *annotation = view.annotation;
selectedAnnotationVariable = annotation.yourProperty;
[self performSegueWithIdentifier:#"performSegue" sender:self];
}

Map view annotations with different pin colors

I have an array with over 200 objects and I am trying to perform a loop through each of them.
Each object will have a yes/no field and I want to display a different coloured marker dependent on that yes / no value.
From what I can see is happening my loop is going through each object first and then all the annotation is added at the end for each object .
Since I perform a check within my loop through the array on the yes no value when all the annotation is added to my map, it will use the yes/no value from the last object in the array when it goes to plot for all.
How can I have it so that the marker will be different dependent on the yes/no value for each individual element?
My code is
for (i = 0; i < [appDelegate.itemArray count]; i++) {
item_details *tempObj = [appDelegate.itemArray objectAtIndex:i];
location.latitude = [tempObj.lat floatValue];
location.longitude = [tempObj.lon floatValue];
current_yesno = tempObj.yesno;
MapViewAnnotation *newAnnotation = [[MapViewAnnotation alloc]initWithTitle:tempObj.name andCoordinate:location];
[self.mapView addAnnotation:newAnnotation];
[newAnnotation release];
}
with my annotation code as follows
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{
MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentloc"];
if(current_yesno == YES){
annView.pinColor = MKPinAnnotationColorGreen;
}
else
{
annView.pinColor = MKPinAnnotationColorRed;
}
annView.animatesDrop=NO;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
return annView;
}
and current_yesno is declared in my .h file.
The viewForAnnotation delegate method isn't necessarily called immediately after you do addAnnotation and it can also be called at other times by the map view when it needs to get the view for an annotation (while your code is doing something completely different).
So you can't depend on the value of an ivar being in sync with some code outside that delegate method.
Instead, add the yesno property to your custom MapViewAnnotation class, set it when creating the annotation and then access its value in viewForAnnotation through the annotation parameter (ie. the map view is giving you a reference to the exact annotation object it wants the view for).
Example:
MapViewAnnotation *newAnnotation = [[MapViewAnnotation alloc] init...
newAnnotation.yesno = tempObj.yesno; // <-- set property in annotation
[self.mapView addAnnotation:newAnnotation];
Then in viewForAnnotation:
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation
{
if (![annotation isKindOfClass:[MapViewAnnotation class]])
{
// Return nil (default view) if annotation is
// anything but your custom class.
return nil;
}
static NSString *reuseId = #"currentloc";
MKPinAnnotationView *annView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (annView == nil)
{
annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
annView.animatesDrop = NO;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
}
else
{
annView.annotation = annotation;
}
MapViewAnnotation *mvAnn = (MapViewAnnotation *)annotation;
if (mvAnn.yesno)
{
annView.pinColor = MKPinAnnotationColorGreen;
}
else
{
annView.pinColor = MKPinAnnotationColorRed;
}
return annView;
}
MKPinAnnotationView *pin = (MKPinAnnotationView *) [self.mapView dequeueReusableAnnotationViewWithIdentifier: #"id"];
if (pin == nil)
{
pin = [[MKPinAnnotationView alloc] initWithAnnotation: annotation reuseIdentifier: #"id"] ;
}
else
{
pin.annotation = annotation;
}
pin.pinTintColor=[UIColor blueColor];
pin.canShowCallout = true;

GMMGeoTileImageData error with MapKit

In my viewcontroller I use Maps and I load a list of pins.
When I move the map or zoom in or out it, my app crashes and displays this error:
[GMMGeoTileImageData isEqualToString:]: unrecognized selector sent to instance 0x862d3b0
This is my code of the view controller:
- (void)viewDidLoad
{
statoAnn = [[NSMutableString alloc] initWithFormat:#"false"];
//bottone annulla per tornare indietro
UIBarButtonItem *annullaButton = [[[UIBarButtonItem alloc] initWithTitle:#"Annulla" style:UIBarButtonItemStylePlain target:self action:#selector(backView)] autorelease];
self.navigationItem.leftBarButtonItem = annullaButton;
//inizializzo la mappa
mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, 320, 416)];
mapView.delegate = self;
mapView.mapType = MKMapTypeStandard;
[self.view addSubview:mapView];
[self setGmaps:arrData];
[super viewDidLoad];
}
/** inizializzo l'annotation del poi mappa **/
- (void) setGmaps:(NSMutableArray*)inputData {
// setto la lat e lng
CLLocationDegrees latitude;
CLLocationDegrees longitude;
CLLocationCoordinate2D poiLocation;
arrAnn = [[NSMutableArray alloc] init];
for(int i=0; i<[inputData count]; i++) {
//ricavo la lat e lng del pin
latitude = [[[inputData objectAtIndex:i] objectForKey:#"latitude"] doubleValue];
longitude = [[[inputData objectAtIndex:i] objectForKey:#"longitude"] doubleValue];
// setto la location del poi
poiLocation.latitude = latitude;
poiLocation.longitude = longitude;
//[[[CLLocation alloc] initWithLatitude:latitude longitude:longitude] autorelease];
//setto il pin
Annotation *ann = [[Annotation alloc] initWithCoordinate:poiLocation];
ann.title = [[inputData objectAtIndex:i] objectForKey:#"label"];
[arrAnn addObject:ann];
[ann release];
}
if (nil != self.arrAnn) {
[self.mapView addAnnotations:arrAnn];
//self.ann = nil;
self.arrAnn = nil;
}
}
/** setto il pin nella mappa ***/
- (void)setCurrentLocation:(CLLocation *)location {
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center = location.coordinate;
region.span.longitudeDelta = 0.1f;
region.span.latitudeDelta = 0.1f;
[self.mapView setRegion:region animated:YES];
[self.mapView regionThatFits:region];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapViewTemp viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *view = nil; // return nil for the current user location
view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"identifier"];
if (nil == view) {
view = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"identifier"] autorelease];
view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
[view setPinColor:MKPinAnnotationColorPurple];
[view setCanShowCallout:YES];
[view setAnimatesDrop:YES];
if (![statoAnn isEqualToString:#"true"]) {
CLLocation *location = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude
longitude:annotation.coordinate.longitude];
[self setCurrentLocation:location];
statoAnn = [NSMutableString stringWithFormat:#"true"];
}
return view;
}
In viewForAnnotation, this line:
statoAnn = [NSMutableString stringWithFormat:#"true"];
sets statoAnn to an autoreleased string.
When the method exits, release is called on statoAnn and it no longer owns the memory it was pointing to. When the method is called again when you zoom or move the map, the memory that statoAnn was pointing to is now used by something else (GMMGeoTileImageData in this case). That object is not an NSString and doesn't have an isEqualToString: method and you get the error you are seeing.
To fix this, set statoAnn so the value is retained like you are doing in viewDidLoad. For example, you could change it to:
statoAnn = [[NSMutableString alloc] initWithFormat:#"true"];
You could also declare statoAnn as a property (#property (nonatomic, copy) NSString *statoAnn) and just set it using self.statoAnn = #"true";. The property setter will do the retaining for you.
However, you don't need to use a string to hold a "true" and "false" value. It's much easier and efficient to use a plain BOOL and you won't have to worry about retain/release since it's a primitive type and not an object.
The other thing is that viewForAnnotation is not the right place to be setting the map view's region in the first place. You can do that in viewDidLoad after the annotations are added.
Another thing: At the top of viewForAnnotation, you have the comment "return nil for the current user location" but that code doesn't do that. It just initializes the view to nil. To actually do what the comment says, you need this:
MKPinAnnotationView *view = nil;
// return nil for the current user location...
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
Finally, if the dequeueReusableAnnotationViewWithIdentifier does return a view (if view != nil), you need to set view.annotation to the current annotation since the re-used view may have been for a different annotation.

how to put MKAnnotation without affecting the MKMapView performance

I have more than 2800 locations to be put on my map.
But when i am putting them the map is freezing. i can do nothing but wait until all the annotation data is available.
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{
NSAutoreleasePool *pool_mr = [[NSAutoreleasePool alloc] init];
NSLog(#"mapView:regionDidChangeAnimated:");
NSLog(#"latitude: %f, longitude: %f", regionsMapView.centerCoordinate.latitude, regionsMapView.centerCoordinate.longitude);
NSLog(#"latitudeDelta: %f, longitudeDelta: %f", regionsMapView.region.span.latitudeDelta, regionsMapView.region.span.longitudeDelta);
if (regionsMapView.region.span.latitudeDelta < 0.007) {
NSLog(#"SHOW ANNOTATIONS");
NSArray *annotations = [regionsMapView annotations];
AddressAnnotation *annotation = nil;
for (int i=0; i<[annotations count]; i++)
{
NSLog(#"%i", i);
annotation = (AddressAnnotation*)[annotations objectAtIndex:i];
[[regionsMapView viewForAnnotation:annotation] setHidden:NO];
}
}else {
NSLog(#"HIDE ANNOTATIONS");
NSArray *annotations = [regionsMapView annotations];
AddressAnnotation *annotation = nil;
for (int i=0; i<[annotations count]; i++)
{
NSLog(#"%i", i);
annotation = (AddressAnnotation*)[annotations objectAtIndex:i];
[[regionsMapView viewForAnnotation:annotation] setHidden:YES];
}
}
[pool_mr release];
}
And the Another method is like below:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation, AddressAnnotation>) annotation {
//NSAutoreleasePool *pool5 = [[NSAutoreleasePool alloc] init];
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]]){
NSLog(#"MKUserLocation");
return nil;
}
else {
NSLog(#"mapView:viewForAnnotation>>>");
NSLog(#"%#", [annotation markerColor]);
NSLog(#"image: %#", [NSMutableString stringWithFormat:#"MKPinAnnotationView_%#.png",[annotation markerColor]]);
MKPinAnnotationView *annView=[[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:[annotation markerColor]] autorelease];
//annView.pinColor = MKPinAnnotationColorPurple;
UIImage *annotationImage = [[UIImage imageNamed:[NSMutableString stringWithFormat:#"MKPinAnnotationView_%#.png",[annotation markerColor]]] autorelease];
annView.image = annotationImage;
annView.animatesDrop = NO;
annView.canShowCallout = YES;
//annView.draggable = NO;
//annView.highlighted = NO;
annView.calloutOffset = CGPointMake(-5, 5);
return annView;
}
//[pool5 release];
NSLog(#"<<<mapView:viewForAnnotation");
return nil;
}
With that number of annotations, I would cluster them so as you zoom in, you get more detail. I put several thousand on a map that way and it works a treat. There is lots of information about map pin clustering if you search for it.
Here's one commercial option for example (no connection, haven't used) but if you look around you'll see plenty of information on how to implement it yourself.

Add a image to MKPointAnnotation

Is it possible?
Im performing an action here that gives to me a pin. But i need a image. And MKAnnotation seems complicated for me.
- (void)abreMapa:(NSString *)endereco {
NSString *urlString = [NSString stringWithFormat:#"http://maps.google.com/maps/geo?q=%#&output=csv",
[endereco stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSString *locationString = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString]];
NSArray *listItems = [locationString componentsSeparatedByString:#","];
double latitude = 0.0;
double longitude = 0.0;
if([listItems count] >= 4 && [[listItems objectAtIndex:0] isEqualToString:#"200"]) {
latitude = [[listItems objectAtIndex:2] doubleValue];
longitude = [[listItems objectAtIndex:3] doubleValue];
}
else {
//Show error
}
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude;
coordinate.longitude = longitude;
myMap.region = MKCoordinateRegionMakeWithDistance(coordinate, 2000, 2000);
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
[annotation setCoordinate:coordinate];
[annotation setTitle:#"Some Title"];
[myMap addAnnotation:annotation];
// Coloca o icone
[self.view addSubview:mapa];
}
Thanks!
You'll need to set up an MKMapViewDelegate, and implement
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
Here's sample code, stolen from the MapCallouts sample code provided on Apple's developer site. I've modified it to focus on the important details. You can see below that the key is to set the image on an annotation view, and return that annotation view from this method.
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *SFAnnotationIdentifier = #"SFAnnotationIdentifier";
MKPinAnnotationView *pinView =
(MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:SFAnnotationIdentifier];
if (!pinView)
{
MKAnnotationView *annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:SFAnnotationIdentifier] autorelease];
UIImage *flagImage = [UIImage imageNamed:#"flag.png"];
// You may need to resize the image here.
annotationView.image = flagImage;
return annotationView;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
We use dequeueReusableAnnotationViewWithIdentifier to grab an already created view to make reuse of our annotation views. If one isn't returned we create a new one. This prevents us from creating hundreds of MKAnnotationViews if only a few are ever in sight at the same time.