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?)
Related
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;
}
Am trying to add custom annotations on MKMapView and implement custom callOut view on tap of annotation.
Am following this link for the same. The issue is when I add the custom annotation didSelectAnnotationView is called on its own and the popOver callout is shown, even when the user has not clicked the annotation
Here is my code:
-(void)callAddAnnotationsLocal{
[_mapView removeAnnotations:[_mapView annotations]];
CLLocationCoordinate2D coord = {.latitude = 43.3998170679, .longitude = -95.1288472486};
[_mapView addAnnotations:[self annotationsLocal]];
}
-(NSArray *)annotationsLocal{
self.annotArrayLocal = nil;
self.annotArrayLocal = [[NSMutableArray alloc] init];
for (int i=0; i<[arrAmmenities count];i++)
{
MKAnnotationView *propertyAnnotation = [[MKAnnotationView alloc] init];
UIFont *font = [UIFont fontWithName:#"Arial" size:18];
NSDictionary *userAttributes = #{NSFontAttributeName: font,
NSForegroundColorAttributeName: [UIColor blackColor]};
NSString *latStr = [NSString stringWithFormat:#"%#",[[arrAmmenities objectAtIndex:i]objectForKey:#"latitude"]];
float latitude = [latStr floatValue];
NSString *longStr = [NSString stringWithFormat:#"%#",[[arrAmmenities objectAtIndex:i]objectForKey:#"longitude"]];
float longitude = [longStr floatValue];
CLLocationCoordinate2D coord = {.latitude = latitude, .longitude = longitude};
PinAnnotation * annotation = [[PinAnnotation alloc] init];
[annotation setCoordinate:coord];
[annotation setType:[[arrAmmenities objectAtIndex:i] objectForKey:#"category"]];
[annotation setTag:i];
[self.mapView addAnnotation:annotation];
[self.annotArrayLocal addObject:propertyAnnotation];
}
return _annotArrayLocal;
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation {
MKAnnotationView *annotationView;
NSString *identifier;
if ([annotation isKindOfClass:[PinAnnotation class]]) {
// Pin annotation.
identifier = #"Pin";
annotationView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
if([[(PinAnnotation *)annotation type] isEqualToString:#"Gas Stations"])
annotationView.image = [UIImage imageNamed:#"marker_gas"];
else if([[(PinAnnotation *)annotation type] isEqualToString:#"Golf Courses"])
annotationView.image = [UIImage imageNamed:#"marker_golf"];
else if([[(PinAnnotation *)annotation type] isEqualToString:#"Restaurants"])
annotationView.image = [UIImage imageNamed:#"marker_restaurant"];
else
annotationView.image = [UIImage imageNamed:#"marker_hospital"];
annotationView.canShowCallout = NO;
}
return annotationView;
}
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
// [mapView deselectAnnotation:view.annotation animated:YES];
MapAnnotViewController *controller = [[UIStoryboard storyboardWithName:#"MapStoryboard" bundle:nil] instantiateViewControllerWithIdentifier:#"sidMapAnnotVC"];
// controller.annotation = view.annotation; // it's useful to have property in your view controller for whatever data it needs to present the annotation's details
wyPopController = [[WYPopoverController alloc] initWithContentViewController:controller];
wyPopController.delegate = self;
[wyPopController setPopoverContentSize:CGSizeMake(200.0, 100.0)];
[wyPopController presentPopoverFromRect:view.frame
inView:view.superview
permittedArrowDirections:WYPopoverArrowDirectionAny
animated:YES];
}
Where am I getting wrong? How do I solve this?
A couple of thoughts:
In annotationsLocal, you are not only instantiating PinAnnotation objects and adding them to a map, but you are also instantiating MKAnnotationView objects, building an array of them, and then returning that array, which is then added as annotations to the map view.
That second array of MKAnnotationView should not be built at all, and certainly shouldn't be added as annotations of the map view. Don't conflate annotation objects (which represent the coordinates of points on a map) and annotation view objects (which represent visual representation of those annotations as they are rendered on the map).
As an aside, and unrelated to your problem, you are instantiating unnecessary annotation views in viewForAnnotation, too. You should dequeue an annotation view, and only instantiate a new one if you didn't successfully retrieve one that was to be reused:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation {
MKAnnotationView *annotationView;
if ([annotation isKindOfClass:[PinAnnotation class]]) {
// Pin annotation.
NSString *identifier = #"Pin";
annotationView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView) {
annotationView.annotation = annotation
} else {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.canShowCallout = NO;
}
if ([[(PinAnnotation *)annotation type] isEqualToString:#"Gas Stations"])
annotationView.image = [UIImage imageNamed:#"marker_gas"];
else if([[(PinAnnotation *)annotation type] isEqualToString:#"Golf Courses"])
annotationView.image = [UIImage imageNamed:#"marker_golf"];
else if([[(PinAnnotation *)annotation type] isEqualToString:#"Restaurants"])
annotationView.image = [UIImage imageNamed:#"marker_restaurant"];
else
annotationView.image = [UIImage imageNamed:#"marker_hospital"];
}
return annotationView;
}
But I'm not seeing anything here that would cause didSelectAnnotationView to be called, unless that incorrect process of adding annotation views as annotations had some weird side effect that caused this. If you're still seeing didSelectAnnotationView called, I might suggest adding a breakpoint there and looking at the call stack and seeing if you can see where that was being invoked. But hopefully the fixing of annotationsLocal will fix it.
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...
}
}
}
This problem has been bugging me for a couple of weeks already!
I have a tab bar application. On one tab I am entering points, and on another tab, the points are displayed on a map. Pins should be different dependent on the types of points.
The problem that I face is that every time I switch from one tab to another, the pin images change from what they should be to other images. For example, if I have four points on the map, three displayed as a circle and one as a triangle, the triangle will be moving around from one point to another. Images seem to change quite randomly.
So, this is the code:
ViewController.m
-(void) viewWillAppear:(BOOL)animated
{
// Select the type of map
if (isMapSelected == NO) {
self.mapView.mapType = MKMapTypeSatellite;
}
else {
self.mapView.mapType = MKMapTypeStandard;
}
// Add region to the map (center and span)
[self addRegion];
// Removing old annotation
[self.mapView removeAnnotations:mapLocations];
// Initializing arrays for the annotations
mapLocations = [[NSMutableArray alloc]init];
[self addAnnotation];
}
-(void) addAnnotation
{
CLLocationCoordinate2D mapLocation;
IGAMapAnnotation *mapAnnotation;
// Calculate how many points are included
NSInteger numberOfPoints = [coordinatesTempArray count];
// Annotations will be added only of the flight plan includes at least one point
if (numberOfPoints > 0)
{
// Trying to add coordinates from the array of coordinates
for (NSInteger i=0; i < ([coordinatesTempArray count]); i++) {
mapAnnotation = [[IGAMapAnnotation alloc]init];
// Taking a point in the array and getting its coordinates
self.mapCoordinates = [coordinatesTempArray objectAtIndex:i];
// Getting a point in the array and getting its lattitude and longitude
self.mapLatitude = [[self.mapCoordinates objectAtIndex:0]doubleValue];
self.mapLongitude = [[self.mapCoordinates objectAtIndex:1]doubleValue];
// Assigning the point coordinates to the coordinates to be displayed on the map
mapLocation.latitude = self.mapLatitude;
mapLocation.longitude = self.mapLongitude;
// Adding coordinates and title to the map annotation
mapAnnotation.coordinate = mapLocation;
mapAnnotation.title = [navaidNamesTempArray objectAtIndex:i];
mapAnnotation.subtitle = nil;
mapAnnotation.navaidType = [navaidTypesTempArray objectAtIndex:i];
// Adding the annotation to the array that will be added to the map
[mapLocations addObject:mapAnnotation];
}
// Adding annotations to the map
[self.mapView addAnnotations:mapLocations];
}
}
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if([annotation isKindOfClass:[IGAMapAnnotation class]])
{
IGAMapAnnotation *myLocation = (IGAMapAnnotation *) annotation;
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"IGAMapAnnotation"];
if (annotationView == nil)
annotationView = myLocation.annotationView;
else
annotationView.annotation = annotation;
return annotationView;
}
else
return nil;
}
IGAMapAnnotation.m
#synthesize coordinate = _coordinate;
#synthesize title = _title;
#synthesize subtitle = _subtitle;
#synthesize type = _type;
// Tried to have this init method but was never able to make it work. Without it, the program crashes too!!!!
-(id)initWithTitle:(NSString *)newTitle Type:(NSString *)type Location:(CLLocationCoordinate2D) newCoordinate
{
self = [super init];
if (self) {
_title = newTitle;
_coordinate = newCoordinate;
_type = type;
}
return self;
}
-(MKAnnotationView *) annotationView {
MKAnnotationView *annotationView = [[MKAnnotationView alloc]initWithAnnotation:self reuseIdentifier:#"IGAMapAnnotation"];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([self.type isEqual: #"A"] || [self.type isEqual: #"B"] || [self.type isEqual: #"C"])
{
annotationView.image = [UIImage imageNamed:#"circle.png"];
}
else if ([self.type isEqual: #"D"])
{
annotationView.image = [UIImage imageNamed:#"triangle.png"];
}
else if ([self.type isEqual: #"E"])
{
annotationView.image = [UIImage imageNamed:#"square.png"];
}
else
{
annotationView.image = [UIImage imageNamed:#"oval.png"];
}
return annotationView;
}
#end
This is it. So far, the behaviour makes no sense to me.
Thanks for your help!
It sounds like an annotation view re-use issue.
When the annotations are re-displayed, they are re-using views with the images of previous annotations. The image property in the view is not being updated as it should be when it is re-used for another annotation.
In the viewForAnnotation delegate method, this code looks wrong:
MKAnnotationView *annotationView = [mapView dequeue...
if (annotationView == nil)
annotationView = myLocation.annotationView;
else
annotationView.annotation = annotation;
If the dequeue returns a view (ie. a previously-created view that may have been created for an annotation of a different type), its annotation property is updated but its image property is not updated.
The existing code only sets the image property when creating a new annotation view (when dequeue returns nil).
Right now, the annotation view creation and image-setting code is in the annotation model class IGAMapAnnotation. It would be better to create a custom MKAnnotationView class that automatically updates the image property whenever its annotation property is updated.
However, another alternative is to put all the logic in the viewForAnnotation delegate method itself (and remove the annotationView method from the IGAMapAnnotation class).
Example of the updated viewForAnnotation delegate method:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if (! [annotation isKindOfClass:[IGAMapAnnotation class]])
{
//return default view if annotation is NOT of type IGAMapAnnotation...
return nil;
}
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"IGAMapAnnotation"];
if (annotationView == nil)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"IGAMapAnnotation"];
//these properties don't change per annotation
//so they can be set only when creating a new view...
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
else
{
annotationView.annotation = annotation;
}
//whether we are using a completely new view or a re-used view,
//set the view's image based on the current annotation...
IGAMapAnnotation *myLocation = (IGAMapAnnotation *) annotation;
if ([myLocation.type isEqual: #"A"] || [myLocation.type isEqual: #"B"] || [myLocation.type isEqual: #"C"])
{
annotationView.image = [UIImage imageNamed:#"circle.png"];
}
else if ([myLocation.type isEqual: #"D"])
{
annotationView.image = [UIImage imageNamed:#"triangle.png"];
}
else if ([myLocation.type isEqual: #"E"])
{
annotationView.image = [UIImage imageNamed:#"square.png"];
}
else
{
annotationView.image = [UIImage imageNamed:#"oval.png"];
}
return annotationView;
}
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!