How to trigger MKAnnotationView's callout view without touching the pin?
Asked Answered
B

13

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.

Burtonburty answered 11/6, 2009 at 1:30 Comment(1)
My solution in other question: https://mcmap.net/q/266789/-wanted-how-to-reliably-consistently-select-an-mkmapview-annotationWatertight
B
34

Ok, here's the solution to this problem.

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

Burtonburty answered 11/6, 2009 at 6:51 Comment(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. :(Ecdysis
@Joe I believe you must remove the annotation before recalling in order for the animation to display.Azalea
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.Lodestar
A
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.

Auria answered 13/9, 2009 at 18:13 Comment(6)
Nothing tricky. Just needed to know to call: [mapView selectAnnotation] rather than [annotation setSelected]. Thanks!Reflect
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]; }Van
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.Pitanga
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.Sonia
@LeandroAlves This is the correct solution. Thank you. Downvoted the root answer for the very same reason you described.Redware
@Reflect : That was real quick n dirty technique. I am your fan :) no gimmicks, works as charmPlayboy
B
34

Ok, here's the solution to this problem.

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

Burtonburty answered 11/6, 2009 at 6:51 Comment(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. :(Ecdysis
@Joe I believe you must remove the annotation before recalling in order for the animation to display.Azalea
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.Lodestar
I
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];
}
Isolecithal answered 15/11, 2010 at 2:58 Comment(2)
This would be the solution that worked for me! thank you much!Sen
Careful! This delegate method is called for annotations that are currently visible i.e. those that are within the map region that is currently displayed on the screen.Watertight
R
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];
        }
    }
}
Roemer answered 26/5, 2011 at 22:16 Comment(2)
I found this to be the best way to do it as this method will be fired upon adding the annotation.Distasteful
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?Dealate
S
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.

Statistical answered 1/8, 2012 at 21:19 Comment(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.Plutonic
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.Peculation
S
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.

Solingen answered 24/8, 2009 at 2:10 Comment(1)
based on this I got [mapView selectAnnotation:[mapView.annotations objectAtIndex:0] animated:false]; to work to pick the first annotation.Ramose
D
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];

Delegation answered 10/9, 2009 at 20:28 Comment(0)
S
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.

Sheepshank answered 1/9, 2013 at 2:29 Comment(0)
C
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.

Calceolaria answered 11/9, 2010 at 7:6 Comment(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?Plutonic
S
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.

Swimming answered 30/1, 2010 at 6:30 Comment(0)
M
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.

Methylnaphthalene answered 16/3, 2010 at 8:25 Comment(0)
F
0

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

Fatal answered 8/2, 2017 at 6:38 Comment(1)
Unfortunately that didn't work me at MacCatalyst (Xcode 15.0.1) I suppose it could be also not working on current iOS17.*Bear
A
-1

Resetting the annotations also will bring the callout to front.

    [mapView removeAnnotation: currentMarker];
    [mapView addAnnotation:currentMarker];
Alienism answered 31/3, 2016 at 5:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.