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!

iDroid
  • 10,403
  • 1
  • 19
  • 27
Ravi Vooda
  • 5,216
  • 5
  • 19
  • 25
  • 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. – yunas Mar 08 '13 at 11:01
  • - (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? – Ravi Vooda Mar 08 '13 at 11:06

3 Answers3

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.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hi, This doesn't seem to be working for iOS7 and up. Any suggestions for a fix for iOS8? – roshi Oct 30 '14 at 23:13
  • @roshi 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. – Rob Oct 31 '14 at 03:35
  • @Rob Can you please take a look at http://stackoverflow.com/questions/30396754/ios-custom-annotation-a-view-below-the-annotation-pin & help me to solve my issue – iYoung May 23 '15 at 07:54
  • @Rob this turns my user location into a pin. Can I prevent this? As well as prevent it being able to be clicked? – user1282637 Sep 04 '15 at 01:27
  • 1
    @user1282637 Check `if ([annotation isKindOfClass:[MKUserLocation class]]) ...` as shown in the revised example snippet above. – Rob Sep 06 '15 at 10:18
  • Good answer but I think you could have summarized it a bit more than this. – technophyle Dec 01 '15 at 02:50
  • @Rob Good Job, Can't we make tappable hole title View? (Not limited to detail button) ? – Jack Sep 15 '18 at 13:57
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)
    }
}
RhodanV5500
  • 1,087
  • 12
  • 16
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];

} 
IronManGill
  • 7,222
  • 2
  • 31
  • 52
Bajaj
  • 859
  • 7
  • 17
  • Can you tell me where should I implement these? MKMapView Controller? – Ravi Vooda Mar 08 '13 at 11:33
  • @RaviVooda yaa Are imposed on the coding of MapViewpart. any other isssue u can ask me :) – Bajaj Mar 08 '13 at 12:04
  • @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). – Rob Mar 08 '13 at 15:52