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.
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;
}
i am using multiple annotation views with different images in my map view. Images are loading properly in simulator but not in device.i am not able to figure out the reason for this issue..following is my code
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString * const annotationIdentifier = #"CustomAnnotation";
annotationView1 = [mapUrCommView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
MKAnnotationView *annotationView2 = nil;
if (annotationView1)
{
annotationView1.annotation = annotation;
if(mapUrCommView.userInteractionEnabled)
annotationView1.image = [UIImage imageNamed:#"MapPinDarkBlue75#2x.png"];// map-pin-black.png
else
{
//if(annotationView1.annotation == self.currentAnnotation|| annotation == self.previousAnnotation)
NSLog(#"in annotation view 1");
annotationView1.image = [UIImage imageNamed:#"mapDrawPoint#2x.png"];// Bluedot.png
}
annotationView1.tag=111;
return annotationView1;
}
else
{
annotationView2 = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
annotationView2.image = [UIImage imageNamed:#"MapPinDarkBlue75#2x.png"];// map-pin-black.png
annotationView2.canShowCallout = YES;
return annotationView2;
}
return nil;
}
here i am drawing physical boundaries for a location in my app...so annotation view 1 is view which i am drawing presently & annoation view 2 will be having all annotations custom view images(all annotations will be having same images) which i already drawn in past..custom annotaion images are loading fine in simulator but not in device
It's worked me for using custom class for annotation.. Please refer this sample Apple documentation.
1.) Allocate MapAnnotation
mapAnnotation = [[MapAnnotation alloc]init];
2.) Set Coordinate for your lat and long
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake([[NBCGPSLocation sharedInstance].gpsLatitude doubleValue], [[NBCGPSLocation sharedInstance].gpsLongitude doubleValue]);
[mapAnnotation setCoordinate:coord];
[self.mapSDKView addAnnotation:mapAnnotation];
3.) Now this delegate method will call once you go that lat and long location
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[CustomAnnotation class]])
{
if(!customAnnotationView)
customAnnotationView = [[MKAnnotationView alloc]init];
customAnnotationView.image = [UIImage imageNamed:#"locationCustom"];
customAnnotationView.annotation = annotation;
return customAnnotationView;
}
else
{
if(!annotationView)
annotationView = [[MKAnnotationView alloc]init];
annotationView.image = [UIImage imageNamed:#"locationIcon"];
annotationView.annotation = annotation;
return annotationView;
}
}
I'm trying to change my pin images in my map view. The annotations are created from parsing a KML file as below:
-(void)loadMap
{
NSURL *url = [NSURL fileURLWithPath:path];
kmlParser = [[KMLParser alloc] initWithURL:url];
[kmlParser parseKML];
NSArray *overlays = [kmlParser overlays];
[map addOverlays:overlays];
NSArray *annotations = [kmlParser points];
[map addAnnotations:annotations];
MKMapRect goTo = MKMapRectNull;
for (id <MKOverlay> overlay in overlays) {
if (MKMapRectIsNull(goTo)) {
goTo = [overlay boundingMapRect];
} else {
goTo = MKMapRectUnion(goTo, [overlay boundingMapRect]);
}
}
for (id <MKAnnotation> annotation in annotations) {
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0);
if (MKMapRectIsNull(goTo)) {
goTo = pointRect;
} else {
goTo = MKMapRectUnion(goTo, pointRect);
}
}
map.visibleMapRect = goTo;
}
The code then runs the viewForAnnotation method below which should change my pin image to my custom pin image.
-(MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *pinView = [kmlParser viewForAnnotation:annotation];
if(annotation != map.userLocation)
{
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView* pinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
pinView.canShowCallout = YES;
pinView.animatesDrop = YES;
pinView.image = [UIImage imageNamed:#"bigPin.png"];
}
return pinView;
}
The viewForAnnotation method runs and it appears to change the image, but the map still shows the original pin image. What am I doing wrong?
The answer provided by Anna above solved the problem
See stackoverflow.com/questions/9814988/…. Code is creating an MKPinAnnotationView instead of plain MKAnnotationView.
The MKPinAnnotationView class provides a concrete annotation view that displays a pin icon like the ones found in the Maps application. Using this class, you can configure the type of pin to drop and whether you want the pin to be animated into place.This Class is inherited from MKAnnotationView but dont have image property itself.You can use the MKAnnotationView class as is or subclass it to provide custom behavior as needed. The image property of the class lets you set the appearance of the annotation view without subclassing directly.
So instead of Using MKPinAnnotationView use MKAnnotationView.
Check out this code for custom image rather than the default image of ios for pin annotations.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if (annotation == mapView.userLocation)
{
return nil;
}
else
{
MKAnnotationView *pinView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"annot"];
if (!pinView)
{
// if an existing pin view was not available, create one
MKAnnotationView *customPinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"annot"];
customPinView.image = [UIImage imageNamed:#"yourImage.png"];
return customPinView;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
}
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!
On initial load, the annotations show just fine. But if I scroll the map, they all disappear and the code is only called for the user location, not the other annotations in the viewForAnnotation delegate method.
Plot Pins
-(void)viewDidLoad{
...Download Coordinates and Data from Web here...
[self.mapView addAnnotation:pin];
}
Delegate Method
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
//Player's Pin
if([annotation class] == MKUserLocation.class) {
return nil;
}
//Cluster Pin
if([annotation isKindOfClass:[REVClusterPin class]]){
REVClusterPin *pin = (REVClusterPin *)annotation;
if( [pin nodeCount] > 0 ){
pin.title = #"___";
MKAnnotationView *annotationView = (REVClusterAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:#"cluster"];
if( !annotationView ){
annotationView = (REVClusterAnnotationView*)
[[REVClusterAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"cluster"];
}
annotationView.image = [UIImage imageNamed:#"cluster.png"];
[(REVClusterAnnotationView*)annotationView setClusterText:
[NSString stringWithFormat:#"%i",[pin nodeCount]]];
annotationView.canShowCallout = NO;
return annotationView;
}
}
//Player Pin
if([annotation isKindOfClass:[ZEPointAnnotation class]]){
ZEPointAnnotation *pin = (ZEPointAnnotation *)annotation;
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"pin"];
if(!annotationView){
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pin"];
}
annotationView.canShowCallout = YES;
annotationView.draggable = NO;
...Create Pin Data Here...
return annotationView;
}
return nil;
}
remove animations. that will solve your problem