Detect when a second annotation is selected in a MKMapView
Asked Answered
B

2

10

When a use selects an annotation in a map, I show a bottom view with informations, such as the google maps app. I'm showing it in the map's delegate :

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view

When the user deselects it (by taping anywhere on the map), I hide my bottom view. This is done in the opposite delegate method :

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view

It works well, I'm happy with it. However, if a user selects a second annotation (ie: he taps a first annotation, then taps another one, without deselecting first the annotation in the meantime), I don't want to hide my bottom view then show it again. I'd just like to change informations in it.

However, as mapView:didDeselectAnnotationView: is called before mapView:didSelectAnnotationView:, I can't figure out how to detect the situation I'm describing above.

My question is : how can I detect that a user selects a second annotation OR, how should I solve this problem in any other way ?

Booher answered 15/5, 2014 at 1:4 Comment(1)
Just a question, How about using an array of annotations and manage them inside both didSelect & didDeselect? and have your "currentAnnotation" to know what you are displaying?Transmogrify
O
2

Maybe try put a delay in your didDeselectAnnotationView method to hide your bottomView. You need to store a reference to your last selected annotation view though.

Example:

@interface MyViewController
{
    MKAnnotationView *lastSelectedAnnotationView;
}

@end


@implementation MyViewController

...

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
    ...

    [self updateBottomViewInfoWithAnnotationView:view];

    lastSelectedAnnotationView = view;
}

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
    // ------------------------------------------------------------------
    // perform check to hide bottomView after a delay, to give
    // didSelectAnnotationView a chance to select new annotation
    // ------------------------------------------------------------------

    [self performSelector:@selector(checkShouldHideBottomView:) withObject:view afterDelay:0.5];
}

-(void)checkShouldHideBottomView:(MKAnnotationView *)lastDeselectedAnnotationView
{
    // ----------------------------------------------------------------------
    // Only hide bottom view if user did not select a new annotation or 
    // last selected annotation is the same as the one being deselected
    // ----------------------------------------------------------------------
    if(lastSelectedAnnotationView == nil || lastDeselectedAnnotationView == lastSelectedAnnotationView)
    {
        // hide your bottom view example
        self.bottomView.alpha = 0;

        // clear lastSelectedAnnotationView reference
        lastSelectedAnnotationView = nil;
    }
}
Onofredo answered 15/5, 2014 at 1:40 Comment(4)
I'll just wait a bit to see if there's less "hacky" way to do it, unless, I'll give it a try. Thanks !Booher
I think this is the right idea but I'm not sure you need to keep any references to selected annotations. In didDeselectAnnotationView, schedule the hide method and at the top of didSelectAnnotationView, just call cancelPreviousPerformRequestsWithTarget.Sometimes
I ended up using cancelPreviousPerformRequestsWithTarget, which I didn't know about. Thanks !Booher
@Booher I ran into a similar problem. How did you solve this exactly? In my case I want it to ignore both diddeselect and didselect.Catapult
H
5

This can be accomplished without adding an explicit delay by executing asynchronously on the main queue.

 var currentSelection: MKAnnotationView?
 func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
   currentSelection = view
   *** select code goes here ***
 }

 func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
   DispatchQueue.main.async { [weak self] in
     self?.delayedDeselect(view: view)
   }
 }

 func delayedDeselect(view: MKAnnotationView) {
   if currentSelection == view {
     *** deselect code goes here ***
   }
 }
Haematoblast answered 18/8, 2017 at 17:3 Comment(1)
You can also check if they didn't select a new annotation in the async call by checking if self.mapView.selectedAnnotations.count == 0Oswell
O
2

Maybe try put a delay in your didDeselectAnnotationView method to hide your bottomView. You need to store a reference to your last selected annotation view though.

Example:

@interface MyViewController
{
    MKAnnotationView *lastSelectedAnnotationView;
}

@end


@implementation MyViewController

...

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
    ...

    [self updateBottomViewInfoWithAnnotationView:view];

    lastSelectedAnnotationView = view;
}

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
    // ------------------------------------------------------------------
    // perform check to hide bottomView after a delay, to give
    // didSelectAnnotationView a chance to select new annotation
    // ------------------------------------------------------------------

    [self performSelector:@selector(checkShouldHideBottomView:) withObject:view afterDelay:0.5];
}

-(void)checkShouldHideBottomView:(MKAnnotationView *)lastDeselectedAnnotationView
{
    // ----------------------------------------------------------------------
    // Only hide bottom view if user did not select a new annotation or 
    // last selected annotation is the same as the one being deselected
    // ----------------------------------------------------------------------
    if(lastSelectedAnnotationView == nil || lastDeselectedAnnotationView == lastSelectedAnnotationView)
    {
        // hide your bottom view example
        self.bottomView.alpha = 0;

        // clear lastSelectedAnnotationView reference
        lastSelectedAnnotationView = nil;
    }
}
Onofredo answered 15/5, 2014 at 1:40 Comment(4)
I'll just wait a bit to see if there's less "hacky" way to do it, unless, I'll give it a try. Thanks !Booher
I think this is the right idea but I'm not sure you need to keep any references to selected annotations. In didDeselectAnnotationView, schedule the hide method and at the top of didSelectAnnotationView, just call cancelPreviousPerformRequestsWithTarget.Sometimes
I ended up using cancelPreviousPerformRequestsWithTarget, which I didn't know about. Thanks !Booher
@Booher I ran into a similar problem. How did you solve this exactly? In my case I want it to ignore both diddeselect and didselect.Catapult

© 2022 - 2024 — McMap. All rights reserved.