Wanted: How to reliably, consistently select an MKMapView annotation
Asked Answered
W

7

30

After calling MKMapView's setCenterCoordinate:animated: method (without animation), I'd like to call selectAnnotation:animated: (with animation) so that the annotation pops out from the newly-centered pushpin.

For now, I simply watch for mapViewDidFinishLoadingMap: and then select the annotation. However, this is problematic. For instance, this method isn't called when there's no need to load additional map data. In those cases, my annotation isn't selected. :(

Very well. I could call this immediately after setting the center coordinate instead. Ahh, but in that case it's possible that there is map data to load (but it hasn't finished loading yet). I'd risk calling it too soon, with the animation becoming spotty at best.

Thus, if I understand correctly, it's not a matter of knowing if my coordinate is visible, since it's possible to stray almost a screenful of distance and have to load new map data. Rather, it's a matter of knowing if new map data needs to be loaded, and then acting accordingly.

Any ideas on how to accomplish this, or how to otherwise (reliably) select an annotation after re-centering the map view on the coordinate where that annotation lives?

Clues appreciated - thanks!

Wool answered 10/8, 2009 at 19:53 Comment(0)
P
36

I ran into the same problem, but found what seems like a reliable and reasonable solution:

  1. Implement the delegate method mapView:didAddAnnotationViews:. When I tried selecting the annotation directly within the delegate method, the callout dropped with the pin! That looked odd, so I add a slight delay of a half-second.

    -(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
        [self performSelector:@selector(selectInitialAnnotation)
              withObject:nil afterDelay:0.5];
    }
    
  2. Select the initial annotation as you'd expect, but calling selectAnnotation:animated;

    -(void)selectInitialAnnotation {
        [self.mapView selectAnnotation:self.initialAnnotation animated:YES];
    }
    

It seems that selectAnnotation:animated: is not called under some conditions. Compare with MKMapView docs:

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

Piscary answered 16/2, 2010 at 0:6 Comment(4)
So it's all about the timing! Thanks very much for sharing this. I'll give it a try.Arbela
This seems to work; better than waiting for the map to load - if you're on a slow connection it's ages before the pin annotation is shown. Thanks.Mcqueen
I had to change '@selector(selectInitialAnnotation:)' to '@selector(selectInitialAnnotation)' i.e. remove the colon. I am on iOS 5.1/xcode 4.2.1Neptunian
This is terrible, but seems to be the correct answer. What a gap in the API.Emitter
I
7

A more consistent way than using a fixed timeout is to listen to the regionDidChange callback. Set the center coordinate of the map to the desired annotation, and when the regionDidChange method is called, then select the annotation in the center, to open the callout.

Here's a little video I took of the thing running randomly between 5 pins.

First, goto the center coordinate of the annotation. Let's say the annotation object is named thePin.

- (void)someMethod {
    [map setCenterCoordinate:thePin.coordinate animated:YES];
}

Then in the regionDidChange method, select this annotation.

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
    [map selectAnnotation:thePin animated:YES];
}
Interpretative answered 12/11, 2010 at 5:23 Comment(4)
Sounds natural/elegant! Can't believe I didn't think of this before. Will try this out - thanks!Arbela
This worked! (Yes, I finally got around to trying it.) Thank you!Arbela
Ha! Spoke too soon. Every time the region changes - even by moving it a little bit, the map re-orients again, making it a bit disorienting for the viewer. Hmm.Arbela
are you changing the map region after the regionDidChange callback is made, or just selecting some annotation?Interpretative
A
5

Well just FYI, this is what the docs say,

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

So if I want to call setCenterCoordinate:animated: or setRegion:animated: and then I want to select the annotation by calling, selectAnnotation:animated: , the annotation won't get selected and the callout won't appear beacause of the exact same reason mentioned above in docs, So the way it would be great to have something like, setCenterCoordinate:animated:ComletionBlock but its not there..! The way that worked for me is as below,

 [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut animations:^{
        [self.mapView setCenterCoordinate:location.coordinate animated:YES];
    } completion:^(BOOL finished) {
        [self.mapView selectAnnotation:location animated:YES];
    }];

This will give you a completion block and u can use that to select the annotation.

Algetic answered 14/8, 2013 at 5:43 Comment(1)
Yay completion blocks! Thanks for sharing this. :)Arbela
E
4

What has worked for me was calling selectAnnotation:animated: from the mapView:didAddAnnotationViews: method:

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views;
{
    [mapView selectAnnotation:[[mapView annotations] lastObject] animated:YES];
}

Apple documentation on the same here.

Note that I only had one annotation on the map so [[mapView annotations] lastObject] was fine for my purposes. Your mileage may vary.

Extort answered 2/11, 2009 at 18:44 Comment(1)
Exactly. One annotation is fine. Try a bunch though. It's not consistent. :(Arbela
P
2

I was having a similar problem. I was using the default MKPinAnnotationView with animatesDrop:YES. After I added the annotations to the MKMapView, I was doing this:

[mapView selectAnnotation:[mapView.annotations objectAtIndex:1] animated:YES]

which in the logic of my program, should select the nearest annotation. This wasn't working. I figured out the reason: the annotation view was not on the screen at the time of this select call, because of the pin drop animation. So all I did was set a timer to select the annotation a second later. It's a hack, but it works. I'm not sure if it'll work in every situation though, for instance on a 3G vs. 3Gs. It'd be better to figure out the right callback function to put it in.

selectTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self
               selector:@selector(selectClosestAnnotation) userInfo:nil
               repeats:NO];

- (void)selectClosestAnnotation {
    [mapView selectAnnotation:[mapView.annotations objectAtIndex:1]
             animated:YES];
}
Paddy answered 15/12, 2009 at 17:49 Comment(1)
Brilliant! Hmm, if there's no way to know when a given annotation is "ready" (e.g., if adding it to the map view is not enough), this might be more of a workaround than a hack! I'll give it a try and report back.Arbela
M
1

I've found several problems with all the solutions I saw for my problem (I want to select a annotation when I added it from viewDidAppear):

  1. Using the mapView:didAddAnnotationViews: delegate method.

    This didn't work for me because if the specified annotation is not onscreen, and therefore does not have an associated annotation view, this method has no effect.

  2. Using the mapViewDidFinishLoadingMap: delegate method.

    This didn't work for me either because the map is cached now, so the method is called only once, the first time.

Solution:

John Blackburn was very close but without the mapView:didAddAnnotationViews: method. I just call my own selectAnnotation: method, before I add the annotation, with a delay:

[self.mapView addAnnotation:ann];
[self performSelector:@selector(selectAnnotation:) withObject:ann afterDelay:0.5];

And this is what I do in my selectAnnotation: method:

- (void)selectAnnotation:(id < MKAnnotation >)annotation
{
   [self.mapView selectAnnotation:annotation animated:YES];
}

Tested on two iPhone 3GS (iOS 5 and 6) and an iPhone 5.

Maidservant answered 23/10, 2012 at 8:31 Comment(1)
The delay makes all the difference, indeed!Arbela
D
0

I was having similar difficulty, but was actually not able to make this method work at all, let alone inconsistently.

Here is what I found to work. Maybe it will work for you too: How to trigger MKAnnotationView's callout view without touching the pin?

Duplicature answered 24/8, 2009 at 2:53 Comment(3)
Greetings! Thanks very much for your reply. When I read this, the solution appears to be to call selectAnnotation:animated: ... only I'm already trying that (see my description above) and it doesn't work. Perhaps I'm missing something additional?Arbela
Oh! My bad. You're talking about the part you posted. :) That is, the part where you walk through the annotations and select them without animation. Got it. I'll review more closely and see if this would work in my case. Thanks!Arbela
Alright! I gave this a shot. I'm not sure it does anything different from what I'm already doing though, which turns out to be shorter (not that shorter is always better ... but here it is): - (void)mapView:(MKMapView *)map selectAnnotationAtIndex:(NSInteger)index animated:(BOOL)animated { [map selectAnnotation:[[map annotations] objectAtIndex:index] animated:animated]; } (For me, animated is YES.) So I've already got a list of annotations, and I even know the index. Problem is, if I call it after setting the center coordinate, it might be too SOON. (See original Q.) Thoughts?Arbela

© 2022 - 2024 — McMap. All rights reserved.