MKMapView MKPointAnnotation tap event
Asked Answered
P

3

47

I have a list of annotations (MKPointAnnotation). I have a UIViewController which is for the whole view, MKMapView implementing Controller, which I thought is useful for detecting the users interaction with the map, my own MKPointAnnotation implementing (subclass) Controller which tells how to show the annotation.

However I'm struck at the detection of the tap event by the user.

Googling told me that I have to do something by implementing the following function.

- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control

and also that I have to implement this in some class which implements the MapViewDelegate (Protocol).

But I'm all confused and unable to move forward. Can anyone tell me where to do what?

Sorry for all the fuss!

Preston answered 8/3, 2013 at 10:54 Comment(2)
you have to set the tags for each annotation, so that when you tap any annotation you can get the tag back from annotation and call the respective data member of the array generating these annotations.Smallish
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control { NSLog(@"got here"); } ..... I put this function in all the three controllers but none of them are being called .... can you tell me where should I define this function?Preston
L
108

There are two ways of detecting user interaction with your annotation view. The common technique is to define a callout (that standard little popover bubble that you see when you tap on a pin in a typical maps app) for your MKAnnotationView. And you create the annotation view for your annotation in the standard viewForAnnotation method:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
    if ([annotation isKindOfClass:[MKUserLocation class]])
        return nil;

    MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"loc"];
    annotationView.canShowCallout = YES;
    annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];

    return annotationView;
}

By doing this, you get a callout, but you're adding an right accessory, which is, in my example above, a disclosure indicator. That way, they tap on your annotation view (in my example above, a pin on the map), they see the callout, and when they tap on that callout's right accessory (the little disclosure indicator in this example), your calloutAccessoryControlTapped is called (in my example below, performing a segue to some detail view controller):

- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
    [self performSegueWithIdentifier:@"DetailsIphone" sender:view];
}

That's a very typical user experience on the small iPhone screen.

But, if you don't like that UX and you don't want the standard callout, but rather you want something else to happen, you can define your MKAnnotationView so that a callout is not shown, but instead you intercept it and do something else (for example, on iPad maps apps, you might show some more sophisticated popover rather than the standard callout). For example, you could have your MKAnnotationView not show a callout:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
    if ([annotation isKindOfClass:[MKUserLocation class]])
        return nil;

    MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"loc"];
    annotationView.canShowCallout = NO;

    return annotationView;
}

But you can then manually handle didSelectAnnotationView to detect when a user tapped on your MKAnnotationView, in this example showing a popover:

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
    [mapView deselectAnnotation:view.annotation animated:YES];

    DetailsViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"DetailsPopover"];
    controller.annotation = view.annotation;
    self.popover = [[UIPopoverController alloc] initWithContentViewController:controller];
    self.popover.delegate = self;
    [self.popover presentPopoverFromRect:view.frame
                                  inView:view.superview
                permittedArrowDirections:UIPopoverArrowDirectionAny
                                animated:YES];
}

I include some screen snapshots for the user interface yielded by the above code in my answer here.

Luteal answered 8/3, 2013 at 15:50 Comment(7)
Hi, This doesn't seem to be working for iOS7 and up. Any suggestions for a fix for iOS8?Yaupon
@Yaupon I tested it and this works fine in iOS 8. Did you set your map view's delegate? If you're still having problems, maybe post your own question on S.O. with detailed code samples, description of what you've tried, etc.Luteal
@Luteal Can you please take a look at #30397254 & help me to solve my issueAnnulet
@Luteal this turns my user location into a pin. Can I prevent this? As well as prevent it being able to be clicked?Faulty
@Faulty Check if ([annotation isKindOfClass:[MKUserLocation class]]) ... as shown in the revised example snippet above.Luteal
Good answer but I think you could have summarized it a bit more than this.Malvie
@Luteal Good Job, Can't we make tappable hole title View? (Not limited to detail button) ?Molybdate
R
10

Robs example for Swift 3:

class MapViewController: UIViewController
{
    override func viewDidLoad()
    {
        super.viewDidLoad()

        // Create map view in storyboard
        view.delegate = self
    }
}

extension MapViewController: MKMapViewDelegate
{
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
    {
        let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "annotationView")
        annotationView.canShowCallout = true
        annotationView.rightCalloutAccessoryView = UIButton.init(type: UIButtonType.detailDisclosure)

        return annotationView
    }

    func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)
    {
        guard let annotation = view.annotation else
        {
            return
        }

        let urlString = "http://maps.apple.com/?sll=\(annotation.coordinate.latitude),\(annotation.coordinate.longitude)"
        guard let url = URL(string: urlString) else
        {
            return
        }

        UIApplication.shared.openURL(url)
    }
}
Republican answered 30/11, 2016 at 10:53 Comment(0)
W
6

Add button in this method

-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation 
{
  pinView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
  pinView.canShowCallout = YES;
}

Then callout method
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
      InfoView *infoView = [[InfoView alloc]initWithNibName:@"InfoView" bundle:nil];
        [self.navigationController pushViewController:infoView animated:YES];

} 
Wrath answered 8/3, 2013 at 11:21 Comment(3)
Can you tell me where should I implement these? MKMapView Controller?Preston
@RaviVooda yaa Are imposed on the coding of MapViewpart. any other isssue u can ask me :)Wrath
@RaviVooda You should implement these in the delegate for your MKMapView. You often designate the delegate for the MKMapView to be view controller for the view that has the map view on it. But don't forget to set the map view's delegate (either in code or via Interface Builder).Luteal

© 2022 - 2024 — McMap. All rights reserved.