Set disclosure button on an Array of annotations - ios

I can't figure out how to set a disclosure button on 1 group of annotations.
The annotations are separated in 3 kinds of Arrays.
NSMutableArray *category1 = [[NSMutableArray alloc]init];
NSMutableArray *category2 = [[NSMutableArray alloc]init];
NSMutableArray *category3 = [[NSMutableArray alloc]init];
I add the annotation objects in these arrays like this:
myAnn = [[Annotations alloc]init];
location.latitude = 52.381285;
location.longitude = 4.888740;
myAnn.coordinate = location;
myAnn.title = #"";
myAnn.subtitle = #"";
[category1 addObject:myAnn];
And put them in another array:
[self.locationArrays addObject:category1];
I use a segmented control to show the 3 groups on the map.
currentAnnotation is an integer to use the objectAtIndex.
-(IBAction)setMap:(id)sender
{
int newAnnotations = ((UISegmentedControl *) sender).selectedSegmentIndex;
if (newAnnotations != self.currentAnnotation)
{
[self.myMapView removeAnnotations:[self.locationArrays objectAtIndex:self.currentAnnotation]];
[self.myMapView addAnnotations:[self.locationArrays objectAtIndex:newAnnotations]];
self.currentAnnotation = newAnnotations;
[self.myMapView showAnnotations:[self.locationArrays objectAtIndex:newAnnotations] animated:YES];
}
}
My question is:
How do the objects, for example, in catergory1 get a disclosure button?
And how should I use it with the viewForAnnotation and calloutAccessoryControlTapped method, because I already included all the annotation with a left blue car accessory.
Update
ViewForAnnotation method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"MapVC"];
if (!annotationView)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"MapVC"];
annotationView.canShowCallout = YES;
if ([annotation isKindOfClass:[MKUserLocation class]] || annotation == myMapView.userLocation || annotation == locationManager)
{
return nil;
}
else
{
//Blauw Navigatie Auto...
UIImageView *carView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Driving"]];
UIButton *blueView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44+30)];
blueView.backgroundColor = [UIColor colorWithRed:0 green:0.5 blue:1 alpha:1];
carView.frame = CGRectMake(11, 14, carView.image.size.width, carView.image.size.height);
[blueView addSubview:carView];
annotationView.leftCalloutAccessoryView = blueView;
}
if ([annotation isKindOfClass:[Annotations class]])
{
Annotations *myRightAnn = (Annotations *)annotation;
if (myRightAnn.category == 1)
{
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
else
{
annotationView.rightCalloutAccessoryView = nil;
}
}
}
return annotationView;
}

In your Annotations class (the one that seems to implement MKAnnotation), add a property named "category" (the name has nothing to do with "Objective-C Categories" -- it's just in reference to what you've called your three arrays: category1..3, use a different name if you like):
#interface Annotations : NSObject <MKAnnotation>
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, copy) NSString *title;
#property (nonatomic, copy) NSString *subtitle;
#property (nonatomic, assign) int category; //<-- add this property
#end
Set the annotation's category property to 1, 2, 3, or whatever when creating the annotation:
myAnn = [[Annotations alloc]init];
location.latitude = 52.381285;
location.longitude = 4.888740;
myAnn.coordinate = location;
myAnn.title = #"";
myAnn.subtitle = #"";
myAnn.category = 1; //<-- set to 1 because adding to category1 array
[category1 addObject:myAnn];
Then in viewForAnnotation, check the annotation's category property and set the rightCalloutAccessoryView as needed (below is just a rough idea of the code):
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return;
}
static NSString *reuseId = #"ann";
MKAnnotationView *av = [mapView dequeueReusable...
if (av == nil) {
av = [[MKAnnotationView alloc] initWithAnnotation...
av.image = ...
av.leftCalloutAccessoryView = ...
av.canShowCallout = YES;
//Set other properties here that will be
//the same for ALL annotations...
}
else {
av.annotation = annotation;
}
//Set rightCalloutAccessoryView based on annotation **AFTER**
//the dequeue/alloc+init...
if ([annotation isKindOfClass:[Annotations class]]) {
Annotations *myAnn = (Annotations *)annotation;
if (myAnn.category == 1) {
//set right view to button only for category 1...
av.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
else {
//no right view for any other categories...
av.rightCalloutAccessoryView = nil;
}
}
return av;
}
In calloutAccessoryControlTapped, check the annotation's class, cast it to Annotations and check the category if necessary and handle it as needed:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
if ([view.annotation isKindOfClass:[Annotations class]]) {
Annotations *myAnn = (Annotations *)view.annotation;
if (control == view.leftCalloutAccessoryView) {
//handle left control tap...
}
else
if (control == view.rightCalloutAccessoryView) {
//handle right control tap...
}
}
}

Related

Adding multiple annotations with multiple images on mapview in ios Objective-c?

I am working on an old application and need to make changes in Mapview. Previously we need to show multiple annotations on mapview with same images on each pin but now we have to show different images on annotation view pins to display address. I am using the following code to display annotation pins but it always shows the same image on annotation pins.
Here is my code:
- (MKAnnotationView *) mapView:(MKMapView *)mapView1 viewForAnnotation:(id <MKAnnotation>) annotation
{
NSLog(#"Eventtype Array is %#",eventTypeArray);
MKAnnotationView * pinView = nil;
if(annotation != _mapvw.userLocation)
{
static NSString * defaultPinID = #"pinId";
pinView = (MKAnnotationView *)[_mapvw dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if ( pinView == nil )
{
pinView = [[MKAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:defaultPinID];
}
for ( int i=0; i<[eventTypeArray count]; i++)
{
eventTypeStr = [NSString stringWithFormat:#"%#",
[eventTypeArray objectAtIndex:i]];
NSLog(#"Event Type is %#",eventTypeStr);
if ([eventTypeStr isEqualToString:#"0"])
{
NSLog(#"Eventtype Array is %#",eventTypeStr);
NSLog(#"Event Type is %#",eventTypeStr);
pinView.canShowCallout = YES;
pinView.image = [UIImage imageNamed:#"smiley.png"];
}
else if ([eventTypeStr isEqualToString:#"1"])
{
NSLog(#"Event Type is %#",eventTypeStr);
pinView.canShowCallout = YES;
pinView.image = [UIImage imageNamed:#"dollar1.png"];
}
else if ([eventTypeStr isEqualToString:#"2"])
{
NSLog(#"Event Type is %#",eventTypeStr);
pinView.canShowCallout = YES;
pinView.image = [UIImage imageNamed:#"donation.png"];
}
}
}
return pinView;
}
You’re iterating through your array of event types for every annotation, presumably always ending with the image associated for the last one in eventTypeArray.
Instead, you want the “event type” to be a property of the annotation. Then, when generating your annotation view, you can look at the annotation’s event type to know which image to use.
So, first, you haven’t done so already, you’d have an annotation that has an eventType property:
typedef NS_ENUM(NSUInteger, EventType) {
EventTypeSmiley,
EventTypeDollar,
EventTypeDonation,
};
#interface EventAnnotation: MKPointAnnotation
#property (nonatomic) EventType eventType;
#end
#implementation EventAnnotation
#end
Now, in this case, I’m using an enumeration for my event types, but you can use whatever type you want. (Even if you stick with the event type array, I’d still use an enumeration to excise cryptic 0/1/2 values sprinkled throughout your code.)
Then, when you add annotations to your map, use this new annotation type, not MKPointAnnotation:
EventAnnotation *eventAnnotation = [[EventAnnotation alloc] init];
eventAnnotation.coordinate = coordinate;
eventAnnotation.title = #"Fund raiser";
eventAnnotation.eventType = EventTypeDollar;
Now that all of your annotations are EventAnnotation, you can have your viewForAnnotation act accordingly:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
NSAssert([annotation isKindOfClass:[EventAnnotation class]], #"Was expecting event annotation”); // obviously, handle non-EventAnnotation annotations however you want, but I’m going to catch failures for now
static NSString *identifier = #"EventAnnotation";
MKAnnotationView *annotationView = [_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (!annotationView) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.canShowCallout = YES;
} else {
annotationView.annotation = annotation; // don't forget this line!
}
EventAnnotation *eventAnnotation = (EventAnnotation *)annotation;
switch (eventAnnotation.eventType) {
case EventTypeSmiley:
annotationView.image = [UIImage imageNamed:#"smiley.png"];
break;
case EventTypeDollar:
annotationView.image = [UIImage imageNamed:#"dollar1.png"];
break;
case EventTypeDonation:
annotationView.image = [UIImage imageNamed:#"donation.png"];
break;
}
return annotationView;
}

Callout opening wrong view after zoom in iOS7

Everything is working fine in my app except for one thing: after zooming in and zooming back out, to see the whole map, some callouts open the wrong detailview.
I don't know if I'm missing some code or else.
Using Xcode 5.1.1 for iOS7.
This is what I've got at the moment:
Annotation.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface Annotation: NSObject <MKAnnotation>
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, copy) NSString *title;
#property (nonatomic, copy) NSString *subtitle;
#end
Annotation.m
#import "Annotation.h"
#implementation Annotation
#synthesize coordinate,title,subtitle;
#end
MapView.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface Nameofthemap : UIViewController <MKMapViewDelegate>
#property (strong, nonatomic) IBOutlet MKMapView *Nameofthemap;
#end
MapView.m
#import "MapView.h"
#import "Annotation.h"
#import "InfoViewController.h"
#import "InfoTwoViewController.h"
#interface MapView ()
#property (nonatomic, strong) IBOutlet InfoViewController *InfoViewController;
#property (nonatomic, strong) IBOutlet InfoTwoViewController *InfoTwoViewController;
#end
#define PLACE1_LATITUDE 43.777130;
#define PLACE2_LONGITUDE 10.790018;
#define PLACE2_LATITUDE 43.81471237;
#define PLACE2_LONGITUDE 10.67472765;
#implementation MapView
- (IBAction)changeMapType:(id)sender {
if (_MapView.mapType == MKMapTypeHybrid)
_MapView.mapType = MKMapTypeStandard;
else
_MapView.mapType = MKMapTypeHybrid;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self gotoLocation];
_MapView.showsUserLocation = YES;
}
- (void)gotoLocation
{
MKCoordinateRegion newRegion;
newRegion.center.latitude = PLACE1_LATITUDE;
newRegion.center.longitude = PLACE2_LONGITUDE;
newRegion.span.latitudeDelta = 0.25f;
newRegion.span.longitudeDelta = 0.25f;
[self.MapView setRegion:newRegion animated:YES];
NSMutableArray * locations = [[NSMutableArray alloc] init];
CLLocationCoordinate2D location;
Annotation *myAnn;
Annotation *myAnn2;
//Place1 annotation
myAnn = [[Annotation alloc] init];
location.latitude = PLACE1_LATITUDE;
location.longitude = PLACE1_LONGITUDE;
myAnn.coordinate = location;
myAnn.title = #"Name of the place";
myAnn.subtitle = #"Details";
[locations addObject:myAnn];
//Place2 annotation
myAnn2 = [[Annotation alloc] init];
location.latitude = PLACE2_LATITUDE;
location.longitude = PLACE2_LONGITUDE;
myAnn2.coordinate = location;
myAnn2.title = #"Name of place two";
myAnn2.subtitle = #"Details";
[locations addObject:myAnn2];
[self->_MapView addAnnotations:locations];
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)myAnn {
if ([myAnn isKindOfClass:[MKUserLocation class]])
{
((MKUserLocation *)myAnn).title = #"Your position";
return nil;
}
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pinView"];
if (!pinView) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:myAnn reuseIdentifier:#"pinView"];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.canShowCallout = YES;
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([[myAnn title] isEqualToString:#"Name of the place"]){
[rightButton addTarget:self action:#selector(myAnnClicked:)forControlEvents:UIControlEventTouchUpInside];
}
if ([[myAnn title] isEqualToString:#"Name of place two"]){
[rightButton addTarget:self action:#selector(myAnn2Clicked:)forControlEvents:UIControlEventTouchUpInside];
}
pinView.rightCalloutAccessoryView = rightButton;
}
return pinView;
}
-(IBAction)myAnnClicked:(id)sender
{
InfoViewController *info = [[InfoViewController alloc]init];
[self.navigationController pushViewController:info animated:YES];
}
-(IBAction)myAnn2Clicked:(id)sender
{
InfoTwoController *info2 = [[InfoTwoController alloc]init];
[self.navigationController pushViewController:info2 animated:YES];
}
#end
It's an annotation view re-use issue.
In viewForAnnotation, the button targets are only being set when creating a view (if dequeueReusableAnnotationViewWithIdentifier returns nil).
But if dequeueReusableAnnotationViewWithIdentifier returns a previously-used view, the button target is still whatever was set for the annotation that used the view before.
That previous annotation may not be the same as the current annotation.
So it's possible for annotation "two" to re-use a view that was originally created for annotation "one" and tapping on the already-created button shows the info for "one" instead of "two".
To fix this, two things should be done:
If dequeueReusableAnnotationViewWithIdentifier returns a view (if pinView is not nil), the code must update the view's annotation property to the current annotation.
The button target must be set whether a new view is being created or a dequeued view is being re-used. The easiest way to do this is to move the button creation/setting after the main if and just before the return.
The updated viewForAnnotation would look like this:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)myAnn {
if ([myAnn isKindOfClass:[MKUserLocation class]])
{
((MKUserLocation *)myAnn).title = #"Your position";
return nil;
}
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pinView"];
if (!pinView) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:myAnn reuseIdentifier:#"pinView"];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.canShowCallout = YES;
}
else
{
//1. Re-using a view, update which annotation it's being used for now
pinView.annotation = myAnn;
}
//2. Now pinView is either a new view or re-used view.
//Set its button target based on current annotation...
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([[myAnn title] isEqualToString:#"Name of the place"]){
[rightButton addTarget:self action:#selector(myAnnClicked:)forControlEvents:UIControlEventTouchUpInside];
}
if ([[myAnn title] isEqualToString:#"Name of place two"]){
[rightButton addTarget:self action:#selector(myAnn2Clicked:)forControlEvents:UIControlEventTouchUpInside];
}
pinView.rightCalloutAccessoryView = rightButton;
return pinView;
}
By the way, instead of creating separate methods for each annotation (which can get tedious), use the map view's calloutAccessoryControlTapped delegate method instead.
In fact, right now, the map view is calling both your custom methods and the calloutAccessoryControlTapped delegate method (in which there's no code currently).
In the delegate method, the annotation tapped is accessible via view.annotation.
So in viewForAnnotation, you would just do this:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)myAnn {
if ([myAnn isKindOfClass:[MKUserLocation class]])
{
((MKUserLocation *)myAnn).title = #"Your position";
return nil;
}
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pinView"];
if (!pinView) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:myAnn reuseIdentifier:#"pinView"];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.canShowCallout = YES;
pinView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
else
{
pinView.annotation = myAnn;
}
return pinView;
}
Then in the calloutAccessoryControlTapped delegate method, you can do something like this:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
if ([view.annotation isKindOfClass:[Annotation class]])
{
Annotation *myAnn = (Annotation *)view.annotation;
id vcToPush = nil;
if ([[myAnn title] isEqualToString:#"Name of the place"]) {
vcToPush = [[InfoViewController alloc]init];
}
else if ([[myAnn title] isEqualToString:#"Name of place two"]) {
vcToPush = [[InfoTwoController alloc]init];
}
[self.navigationController pushViewController:vcToPush animated:YES];
}
}
Then remove the myAnnClicked and myAnn2Clicked methods.
You would also be much better off creating a generic "Info" view controller instead of a separate one for each annotation.
Some other unrelated things:
Don't put a semi-colon at the end of the #define lines
You've defined PLACE2_LONGITUDE twice
newRegion.center is using PLACE2_LONGITUDE instead of PLACE1_LONGITUDE

iOS MapClustering: CCHMapClusterController: Problems when zooming out after zooming in. Single annotations show cluster-annotation callouts

I am trying out this new awesome library for annotation clustering called CCHMapClusterConroller.
I reckon there is little or no help to get here, since the library is so new, but I'll give it a shot.
I am having trouble with the annotation callouts and I do not know what I am doing wrong.
When I enter my view controllers view, the annotations are clustered perfectly.
When I then zoom in on my annotations they are declustered as expected.
However, when I zoom back out and the annotations starts to group up to clusters again, some clusters have the image of a single annotation. The same thing goes for their callouts. They have the titles and subtitles of my clustered annotations, but they have the disclosure button which i have only assigned to single, unclustered annotations.
This is how I initialize my cluster controller:
...
// viewDidLoad
self.mapClusterController = nil;
self.mapClusterController = [[CCHMapClusterController alloc] initWithMapView:self.mapView];
self.mapClusterController.delegate = self;
self.mapClusterController.cellSize = 25;
self.mapClusterController.marginFactor = 0.4;
//
...
This is how i populate the controller with my own custom annotations:
//...
[self.mapClusterController removeAnnotations:[self.mapClusterController.annotations allObjects] withCompletionHandler:nil];
NSMutableArray *arrayWithAnnotations = [[NSMutableArray alloc] init];
FFMapAnnotation *annotation;
for(FFPlace *place in self.searchResult){
annotation = [[FFMapAnnotation alloc] init];
annotation.title = place.placeName;
annotation.coordinate = CLLocationCoordinate2DMake(place.placeLatitude, place.placeLongitude);
annotation.place = place;
annotation.subtitle = place.placeAddress;
[arrayWithAnnotations addObject:annotation];
}
[self.mapClusterController addAnnotations:arrayWithAnnotations withCompletionHandler:nil];
//...
Now, I do believe that these two methods work correctly.
I imagine that the problem lies within the viewForAnnotation method.
This is currently how it looks:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView;
if ([annotation isKindOfClass:CCHMapClusterAnnotation.class]) {
static NSString *identifier = #"clusterAnnotation";
annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView) {
annotationView.annotation = annotation;
} else {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.canShowCallout = YES;
}
CCHMapClusterAnnotation *clusterAnnotation = (CCHMapClusterAnnotation *)annotation;
clusterAnnotation.delegate = self;
if(!clusterAnnotation.isCluster){
FFMapAnnotation *annot2 = (FFMapAnnotation*)clusterAnnotation.annotations.allObjects[0];
annotationView.image = [UIImage imageNamed:[DefinedCategories getCategoryInformationForID:annot2.place.placeMainCategoryID].mapImage];
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeContactAdd];
annotationView.rightCalloutAccessoryView.tag = -1;
}else {
return nil;
}
}
return annotationView;
}
// the return nil at the end makes the annotation view a regular MKPinAnnotationView, which I use to visually represent my clusters.
Finally, the two delegate methods for cluster title and subtitle looks like this:
- (NSString *)mapClusterController:(CCHMapClusterController *)mapClusterController titleForMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation
{
NSString *title;
if(mapClusterAnnotation.annotations.count > 1){
title = [NSString stringWithFormat:#"%ld %#", (unsigned long) mapClusterAnnotation.annotations.count, [AppDelegate get:#"PLACES"alter:nil]];
} else{
FFMapAnnotation *annotation = mapClusterAnnotation.annotations.allObjects[0];
return annotation.title;
}
return title;
}
- (NSString *)mapClusterController:(CCHMapClusterController *)mapClusterController subtitleForMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation
{
NSString *title;
if(mapClusterAnnotation.annotations.count > 1){
title = [AppDelegate get:#"ZOOM_FURTHER"alter:nil];
} else{
FFMapAnnotation *annotation = mapClusterAnnotation.annotations.allObjects[0];
return annotation.subtitle;
}
return title;
}
What i want:
What is happening:
Am I missing something vital in the delegate methods? I spent all day debugging this.
EDIT:
Adding the delegate method
- (void)mapClusterController:(CCHMapClusterController *)mapClusterController willReuseMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation
{
MKAnnotationView *annotationView = [_mapView viewForAnnotation:mapClusterAnnotation];
if(!(mapClusterAnnotation.annotations.count == 1)){
annotationView.image = [UIImage imageNamed:#"map_icon_button_thing"];
annotationView.rightCalloutAccessoryView = nil;
}else {
FFMapAnnotation *annotation = mapClusterAnnotation.annotations.allObjects[0];
annotationView.image = [UIImage imageNamed:[DefinedCategories getCategoryInformationForID:annotation.place.placeMainCategoryID].mapImage];
}
}
solved my issues!

Setting Map Pin colour dynamically for iOS

I parse an xml that contains the string 0 ,1 and 2.
//For reference
0 = Green
1 = Red
2 = Purple
I have a class that confirms to the MKAnnotaion that contains the below variables that are properties.
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
MKPinAnnotationColor pinColor;
This class is named MyAnnotation
Now in the viewDidLoad of the map view I run a for loop to iterate over the parsed data
like the below (locationArray holds this data and I pull out all the info just fine.
for (int i = 0; i < locationArray.count; i++) {
myAnnotation =[[MyAnnotation alloc] init];
NSString *thePointName = [[locationArray objectAtIndex:i]xmlName];
NSString *theAddress = [[locationArray objectAtIndex:i]xmlAddress];
NSString *latString = [[locationArray objectAtIndex:i]xmlLat];
NSString *lonString = [[locationArray objectAtIndex:i]xmlLon];
//This is the string that pulls out the mentioned 0, 1 or 2 strings which represent the colour of the pins poinType is retained as a string
pointType = [[locationArray objectAtIndex:i]xmlType];
double theLatitude = [latString doubleValue];
double theLongtitude = [lonString doubleValue];
userLocation.latitude=theLatitude;
userLocation.longitude=theLongtitude;
myAnnotation.coordinate=userLocation;
myAnnotation.title=[NSString stringWithFormat:#"%#", thePointName];
myAnnotation.subtitle=[NSString stringWithFormat:#"%#", theAddress];
//I log that the points are actually giving either of the colors and if so set MyAnnotation class to the pincolor
NSLog(#"Points Color %#", pointType);
if ([pointType isEqualToString:#"0"]){
myAnnotation.pinColor = MKPinAnnotationColorGreen;
}else if ([pointType isEqualToString:#"1"]){
myAnnotation.pinColor = MKPinAnnotationColorRed;
}else if ([pointType isEqualToString:#"2"]) {
myAnnotation.pinColor = MKPinAnnotationColorPurple;
}
[mapView addAnnotation:myAnnotation];
}
Now in the MKAnnotationView view i do the below
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// try to dequeue an existing pin view first
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView* pinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
pinView.animatesDrop=YES;
pinView.canShowCallout=YES;
//set pin color to the correct colour
if (myAnnotation.pinColor = MKPinAnnotationColorGreen) {
pinView.pinColor = MKPinAnnotationColorGreen;
}
else if (myAnnotation.pinColor = MKPinAnnotationColorRed) {
pinView.pinColor = MKPinAnnotationColorRed;
}
else if (myAnnotation.pinColor = MKPinAnnotationColorPurple){
pinView.pinColor = MKPinAnnotationColorPurple;
}
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = rightButton;
UIImageView *profileIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Profile.png"]];
pinView.leftCalloutAccessoryView = profileIconView;
return pinView;
}
I have also tried in the above method however it is not setting the pin colours. Everything else is fine!
//set pin color to the correct colour
if (pointType isEqualToString:#"0") {
pinView.pinColor = MKPinAnnotationColorGreen;
}
else if (pointType isEqualToString:#"1") {
pinView.pinColor = MKPinAnnotationColorRed;
}
else if (pointType isEqualToString:#"2"){
pinView.pinColor = MKPinAnnotationColorPurple;
}
This code in viewForAnnotation:
if (myAnnotation.pinColor = MKPinAnnotationColorGreen) {
will not work for two reasons:
It is using a single equals sign which is for assignment -- not for checking equality. It needs to use two equal signs to check for equality. However, this doesn't fix the main issue which is reason #2...
The code is checking the value of myAnnotation which is a variable set outside this delegate method. There is no guarantee that the delegate method will be called in sync with the for-loop in which myAnnotation is set. Do not assume that viewForAnnotation will be called right after you call addAnnotation. It is even possible for the delegate method to be called multiple times for the same annotation if the map view needs to display the annotation again after a zoom or pan.
Instead, you must use the annotation parameter that is passed to the delegate method. This is a reference to the annotation the map view wants a view for in the current call of the delegate method.
Since the annotation parameter is typed generically as id<MKAnnotation>, you'll first have to cast it to your custom class and then you can access any custom properties:
//first make sure this annotation is our custom class before casting it...
if ([annotation isKindOfClass:[MyAnnotation class]])
{
MyAnnotation *currentAnn = (MyAnnotation *)annotation;
if (currentAnn.pinColor == MKPinAnnotationColorGreen) {
pinView.pinColor = MKPinAnnotationColorGreen;
}
else if (currentAnn.pinColor == MKPinAnnotationColorRed) {
pinView.pinColor = MKPinAnnotationColorRed;
}
else if (currentAnn.pinColor == MKPinAnnotationColorPurple) {
pinView.pinColor = MKPinAnnotationColorPurple;
}
}
or even simpler:
//first make sure this annotation is our custom class before casting it...
if ([annotation isKindOfClass:[MyAnnotation class]])
{
MyAnnotation *currentAnn = (MyAnnotation *)annotation;
pinView.pinColor = currentAnn.pinColor;
}
(Unrelated, but why is the code setting the title of rightButton even though it won't be visible?)

ios-mapkit, strange behavior with custom image annotations

I've written some code for showing annotations with custom images in a mapview.
My mapview delegate implements this method to customize annotation when they are put in the map:
- (MKAnnotationView *) mapView:(MKMapView *) mapView viewForAnnotation:(id<MKAnnotation>) annotation {
if ([annotation isKindOfClass:[Station class]]) {
Station *current = (Station *)annotation;
MKPinAnnotationView *customPinview = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
if([[current type] compare:FONTANELLA]==NSOrderedSame)
customPinview.pinColor = MKPinAnnotationColorPurple;
else{
int test=current.bici;
if(test==0)
customPinview.image = [UIImage imageNamed:#"bicimir.png"];
else if(test<4)
customPinview.image = [UIImage imageNamed:#"bicimi.png"];
else if(test>=4)
customPinview.image = [UIImage imageNamed:#"bicimig.png"];
}
customPinview.animatesDrop = NO;
customPinview.canShowCallout = YES;
return customPinview;
}
else{
NSString *identifier=#"MyLocation";
MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
return annotationView;
}
}
The problem is a strange behavior when I long click on a custom annotation on the map:
The image change and the default red pin is shown.
Why this behavior? And How can I avoid it?
When you want to use a custom image for an annotation view, create a generic MKAnnotationView instead of an MKPinAnnotationView.
The MKPinAnnotationView really likes to display its default image which is a pin.
Rearrange the logic a bit so that for FONTANELLA, it creates an MKPinAnnotationView but for the rest an MKAnnotationView.
Also, you should really implement annotation view re-use for all cases (and the last else part doesn't make sense since nothing is done if the dequeue doesn't return anything--you could just do return nil; instead).
inside .h file
#interface AddressAnnotation : NSObject<MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *mPinColor;
}
#property (nonatomic, retain) NSString *mPinColor;
#end
in .m file
#implementation AddressAnnotation
#synthesize coordinate mPinColor;
- (NSString *)pincolor{
return mPinColor;
}
- (void) setpincolor:(NSString*) String1{
mPinColor = String1;
}
-(id)initWithCoordinate:(CLLocationCoordinate2D) c{
coordinate=c;
NSLog(#"%f,%f",c.latitude,c.longitude);
return self;
}
#end
inside .m class file
- (MKAnnotationView *) mapView:(MKMapView *)mapView1 viewForAnnotation:(AddressAnnotation *) annotation{
UIImage *anImage=[[UIImage alloc] init];
MKAnnotationView *annView=(MKAnnotationView*)[mapView1 dequeueReusableAnnotationViewWithIdentifier:#"annotation"];
if(annView==nil)
{
annView=[[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"annotation"] autorelease];
}
if([annotation.mPinColor isEqualToString:#"green"])
{
anImage=[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Google pin green.png" ofType:nil]];
}
else if([annotation.mPinColor isEqualToString:#"red"])
{
anImage=[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Google pin red.png" ofType:nil]];
}
else if([annotation.mPinColor isEqualToString:#"blue"])
{
anImage=[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Google pin blue.png" ofType:nil]];
}
annView.image = anImage;
return annView;
}

Resources