76

I'm working on a MKMapView with the usual colored pin as the location points. I would like to be able to have the callout displayed without touching the pin.

How should I do that? Calling setSelected:YES on the annotationview did nothing. I'm thinking of simulate a touch on the pin but I'm not sure how to go about it.

Atulkumar V. Jain
  • 5,102
  • 9
  • 44
  • 61
Teo Choong Ping
  • 12,512
  • 18
  • 64
  • 91

13 Answers13

63

But there is a catch to get benvolioT's solution to work, the code

for (id<MKAnnotation> currentAnnotation in mapView.annotations) {       
    if ([currentAnnotation isEqual:annotationToSelect]) {
        [mapView selectAnnotation:currentAnnotation animated:FALSE];
    }
}

should be called from - (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView, and nowhere else.

The sequence in which the various methods like viewWillAppear, viewDidAppear of UIViewController and the - (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView is called is different between the first time the map is loaded with one particular location and the subsequent times the map is displayed with the same location. This is a bit tricky.

sth
  • 222,467
  • 53
  • 283
  • 367
Steve Shi
  • 639
  • 1
  • 5
  • 2
  • 4
    Nothing tricky. Just needed to know to call: [mapView selectAnnotation] rather than [annotation setSelected]. Thanks! – bentford Oct 21 '09 at 01:24
  • Okay this worked the first time automatically. But now it's not working anymore? what the? - (void)mapViewDidFinishLoadingMap:(MKMapView *)_mapView { [mapView selectAnnotation:addAnnotation animated:YES]; } – gotnull Dec 01 '10 at 12:39
  • 4
    mapViewDidFinishLoadingMap is only triggered for the first time as the maps are cached. When the map is loaded from cache, mapViewDidFinishloadingMap isn't triggered anymore. – Niels R. Mar 16 '11 at 09:53
  • 8
    The correct delegate method that assure you that your annotation view is ready to display the callout is the one pointed by @user507778. `mapView:didAddAnnotationViews:`. This ensure the map view is loaded and, also, the annotation views are placed and ready to display the callout bubble. Any other way can work or not, depending on connection speed, cache system, application interruptions and so on. – Leandro Alves Aug 28 '12 at 00:39
  • 1
    @LeandroAlves This is the correct solution. Thank you. Downvoted the root answer for the very same reason you described. – Aleks N. Sep 18 '12 at 17:24
  • @bentford : That was real quick n dirty technique. I am your fan :) no gimmicks, works as charm – Alok C Jul 15 '14 at 14:44
34

Ok, here's the solution to this problem.

To display the callout use MKMapView's selectAnnotation:animated method.

Benjol
  • 63,995
  • 54
  • 186
  • 268
Teo Choong Ping
  • 12,512
  • 18
  • 64
  • 91
  • 3
    This doesn't help. I can call this and it won't always animate the selection. The first time, yes. Every time after that, no - even if the method is called. benvolioT's solution also doesn't work for me. Even if I call selectAnnotation:animated: directly, when the annotation is known, doesn't work. Same issues as before. :( – Joe D'Andrea Sep 02 '09 at 16:13
  • @Joe I believe you must remove the annotation before recalling in order for the animation to display. – Oh Danny Boy Jan 06 '11 at 17:15
  • commenting that if pin is in *selected* state, and you select it again it remains in the same *selected* state. until you de-select it or user de-select it. – dklt Sep 19 '12 at 06:23
32

Assuming that you want the last annotation view to be selected, you can put the code below:

[mapView selectAnnotation:[[mapView annotations] lastObject] animated:YES];

in the delegate below:

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
    //Here
    [mapView selectAnnotation:[[mapView annotations] lastObject] animated:YES];
}
sth
  • 222,467
  • 53
  • 283
  • 367
user507778
  • 321
  • 3
  • 2
21

Ok, to successfully add the Callout you need to call selectAnnotation:animated after all the annotation views have been added, using the delegate's didAddAnnotationViews:

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views{
    for (id<MKAnnotation> currentAnnotation in mapView.annotations) {       
        if ([currentAnnotation isEqual: annotationToSelect]) {
            [mapView selectAnnotation:currentAnnotation animated:YES];
        }
    }
}
Vespassassina
  • 515
  • 3
  • 7
  • I found this to be the best way to do it as this method will be fired upon adding the annotation. – Gavin Aug 16 '11 at 11:01
  • 1
    This method drops the callout together with the pin, is there a solution that can show the callout right after the pin is dropped to the map? – Michael Sep 23 '11 at 13:42
12

After trying a variety of answers to this thread, I finally came up with this. It works very reliably, I have yet to see it fail:

- (void)mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views;
{
    for(id<MKAnnotation> currentAnnotation in aMapView.annotations) 
    {       
        if([currentAnnotation isEqual:annotationToSelect]) 
        {
            NSLog(@"Yay!");
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_current_queue(), ^
            {
                [aMapView selectAnnotation:currentAnnotation animated:YES];
            });
        }
    }
}

The block is used to delay slightly, as without it the callout may not be shown correctly.

charliehorse55
  • 1,940
  • 5
  • 24
  • 38
  • 2
    Unbelievable. It's such a kludge and it's in a race condition, but it works. What blows here is that with the storyboard doing mostly everything, it's not obvious that the delegate needs to be wired up through the storyboard - if you haven't touched MKMapViews in 3 months. This is stuff that the storyboard should do automatically, or at least warn you about. I'd rather define the mapKit ref on the init AND the delegate on the init as well than have the storyboard do 1/2 the job and leave you hanging without knowing that there is another 1/2 of the job left to be done. >1 hr wasted on this. – Alex Zavatone Sep 25 '12 at 18:48
  • This works great to show the callout immediately upon completion of the pin's drop. Without the delay, you see the callout falling with the pin, which looks bizarre. It's unfortunate that "didAddAnnotationViews" is called at the moment the pin starts dropping, not at the moment it lands. – James Toomey Jun 16 '16 at 18:43
9

This does not work for me. I suspect a bug in the MapKit API.

See this link for details of someone else for who this is not working: http://www.iphonedevsdk.com/forum/iphone-sdk-development/19740-trigger-mkannotationview-callout-bubble.html#post110447

--edit--

Okay after screwing with this for a while, here is what I've been able to make work:

for (id<MKAnnotation> currentAnnotation in mapView.annotations) {       
    if ([currentAnnotation isEqual:annotationToSelect]) {
        [mapView selectAnnotation:currentAnnotation animated:FALSE];
    }
}

Note, this requires implementing - (BOOL)isEqual:(id)anObject for your class that implements the MKAnnotation protocol.

benvolioT
  • 4,507
  • 2
  • 36
  • 30
  • based on this I got [mapView selectAnnotation:[mapView.annotations objectAtIndex:0] animated:false]; to work to pick the first annotation. – Andiih Sep 14 '09 at 08:36
6

If you just want to open the callout for the last annotation you added, try this, works for me.

[mapView selectAnnotation:[[mapView annotations] lastObject] animated:YES];

6

The problem with calling selectAnnotation from - (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView is that, as the name implies, this event is only triggered once your MapView loads initially, so you won't be able to trigger the annotation's callout if you add it after the MapView has finished loading.

The problem with calling it from - (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views is that your annotation may not be on-screen when selectAnnotation is called which would cause it to have no effect. Even if you center your MapView's region to the annotation's coordinate before adding the annotation, the slight delay it takes to set the MapView's region is enough for selectAnnotation to be called before the annotation is visible on-screen, especially if you animate setRegion.

Some people have solved this issue by calling selectAnnotation after a delay as such:

-(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
    [self performSelector:@selector(selectLastAnnotation)
        withObject:nil afterDelay:1];
}

-(void)selectLastAnnotation {
    [myMapView selectAnnotation:
        [[myMapView annotations] lastObject] animated:YES];
}

But even then you may get weird results since it may take more than one second for the annotation to appear on-screen depending on various factors like the distance between your previous MapView's region and the new one or your Internet connection speed.

I decided to make the call from - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated instead since it ensures the annotation is actually on-screen (assuming you set your MapView's region to your annotation's coordinate) because this event is triggered after setRegion (and its animation) has finished. However, regionDidChangeAnimated is triggered whenever your MapView's region changes, including when the user just pans around the map so you have to make sure you have a condition to properly identify when is the right time to trigger the annotation's callout.

Here's how I did it:

MKPointAnnotation *myAnnotationWithCallout;

- (void)someMethod {
    MKPointAnnotation *myAnnotation = [[MKPointAnnotation alloc] init];
    [myAnnotation setCoordinate: someCoordinate];
    [myAnnotation setTitle: someTitle];

    MKCoordinateRegion someRegion =
       MKCoordinateRegionMakeWithDistance (someCoordinate, zoomLevel, zoomLevel);

    myAnnotationWithCallout = myAnnotation;
    [myMapView setRegion: someRegion animated: YES];
    [myMapView addAnnotation: myAnnotation];
}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    if (myAnnotationWithCallout)
    {
        [mapView selectAnnotation: myAnnotationWithCallout animated:YES];
        myAnnotationWithCallout = nil;
    }
}

That way your annotation is guaranteed to be on-screen at the moment selectAnnotation is called, and the if (myAnnotationWithCallout) part ensures no region setting other than the one in - (void)someMethod will trigger the callout.

user1088509
  • 61
  • 1
  • 4
5

I read the API carefully and finally I found the problem:


If the specified annotation is not onscreen, and therefore does not have an associated annotation view, this method has no effect.

So you can wait some time (for example, 3 seconds) and then perform this action. Then it works.

user444974
  • 51
  • 1
  • 1
  • 1
    That's creating a race condition. Isn't there an event that indicates that the map has finished displaying that you can use to trigger the display? – Alex Zavatone Sep 25 '12 at 18:30
3

Due to something like the code shown by benvolioT, that I suspect exists in the system, when I used selectAnnotation:animation: method, it did not show the callOut, I guessed that the reason was because it was already selected and it was avoiding from asking the MapView to redraw the callOut on the map using the annotation title and subtitle.

So, the solution was simply to deselect it first and to re-select it.

E.g: First, I needed to do this in Apple's touchMoved method (i.e. how to drag an AnnotationView) to hide the callOut. (Simply using annotation.canShowAnnotation = NO alone does not work, since I suspect that it needs redrawing. The deselectAnnotaiton causes the necessary action. Also, deselecting alone did not do that trick, the callOut disappeared only once and got redrawn straight away. This was the hint that it got reselected automatically).

annotationView.canShowAnnotation = NO;
[mapView deselectAnnotation:annotation animated:YES];

Then, simply using the code below in touchEnded method did not bring back the callOut (The annotation has been automatically selected by the system by that time, and presumably the redrawing of the callOut never occrrs):

annotationView.canShowAnnotation = YES;
[mapView selectAnnotation:annotation animated:YES];

The solution was:

annotationView.canShowAnnotation = YES;
[mapView deselectAnnotation:annotation animated:YES];
[mapView selectAnnotation:annotation animated:YES];

This simply bought back the callOut, presumably it re-initiated the process of redrawing the callOut by the mapView.

Strictly speaking, I should detect whether the annotation is the current annotation or not (selected, which I know it is) and whether the callOut is actually showing or not (which I don't know) and decide to redraw it accordingly, that would be better. I, however, have not found the callOut detection method yet and trying to do so myself is just a little bit unnecessary at this stage.

Yoichi
  • 12,233
  • 2
  • 18
  • 11
1

Steve Shi's response made it clear to me that selectAnnotation has to be called from mapViewDidFinishLoadingMap method. Unfortunately i cannot vote up but i want to say thanks here.

gyozo kudor
  • 6,284
  • 10
  • 53
  • 80
0

Just add [mapView selectAnnotation:point animated:YES];

shamila
  • 1,280
  • 6
  • 20
  • 45
-1

Resetting the annotations also will bring the callout to front.

    [mapView removeAnnotation: currentMarker];
    [mapView addAnnotation:currentMarker];
Zumry Mohamed
  • 9,318
  • 5
  • 46
  • 51