33
votes

I need to draw the current user annotation (the blue dot) on top of all other annotations. Right now it is getting drawn underneath my other annotations and getting hidden. I'd like to adjust the z-index of this annotation view (the blue dot) and bring it to the top, does anyone know how?

Update: So I can now get a handle on the MKUserLocationView, but how do I bring it forward?

- (void) mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views {
    for (MKAnnotationView *view in views) {
        if ([[view annotation] isKindOfClass:[MKUserLocation class]]) {
            // How do I bring this view to the front of all other annotations?
            // [???? bringSubviewToFront:????];
        }
    }
}
9

9 Answers

34
votes

Finally got it to work using the code listed below thanks to the help from Paul Tiarks. The problem I ran into is that the MKUserLocation annotation gets added to the map first before any others, so when you add the other annotations their order appears to be random and would still end up on top of the MKUserLocation annotation. To fix this I had to move all the other annotations to the back as well as move the MKUserLocation annotation to the front.

- (void) mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views 
{
    for (MKAnnotationView *view in views) 
    {
        if ([[view annotation] isKindOfClass:[MKUserLocation class]]) 
        {
            [[view superview] bringSubviewToFront:view];
        } 
        else 
        {
            [[view superview] sendSubviewToBack:view];
        }
    }
}

Update: You may want to add the code below to ensure the blue dot is drawn on top when scrolling it off the viewable area of the map.

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{        
  for (NSObject *annotation in [mapView annotations]) 
  {
    if ([annotation isKindOfClass:[MKUserLocation class]]) 
    {
      NSLog(@"Bring blue location dot to front");
      MKAnnotationView *view = [mapView viewForAnnotation:(MKUserLocation *)annotation];
      [[view superview] bringSubviewToFront:view];
    }
  }
}
26
votes

Another solution: setup annotation view layer's zPosition (annotationView.layer.zPosition) in:

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views;

11
votes

The official answer to that thread is wrong... using zPosition is indeed the best approach and fastest vs using regionDidChangeAnimated...

else you would suffer big performance impact with many annotations on map (as every change of frame would rescan all annotations). and been testing it...

so when creating the view of the annotation (or in didAddAnnotationViews) set : self.layer.zPosition = -1; (below all others)

and as pointed out by yuf: This makes the pin cover callouts from other pins – yuf Dec 5 '13 at 20:25

i.e. the annotation view will appear below other pins.

to fix, simply reput the zPosition to 0 when you have a selection

-(void) mapView:(MKMapView*)mapView didSelectAnnotationView:(MKAnnotationView*)view {
    if ([view isKindOfClass:[MyCustomAnnotationView class]])
        view.layer.zPosition = 0;
   ...
}

-(void) mapView:(MKMapView*)mapView didDeselectAnnotationView:(MKAnnotationView*)view {
    if ([view isKindOfClass:[MyCustomAnnotationView class]])
        view.layer.zPosition = -1;
   ...
}
5
votes

Update for iOS 14

I know it's an old post, but the question is still applicable and you end up here when typing it into your favorite search engine.

Starting with iOS 14, Apple introduced a zPriority property to MKAnnotationView. You can use it to set up the z-index for your annotations using predefined constants or floats. Also, Apple made it possible to finally create the view for the user location on our own and provided MKUserLocationView as a subclass of MKAnnotationView.

From the documentation for MKUserLocationView:

If you want to specify additional configuration, such as zPriority, create this annotation view directly. To display the annotation view, return the instance from mapView(_:viewFor:).

The following code snippet shows how this can be done (add the code to your MKMapViewDelegate):

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    
    // Alter the MKUserLocationView (iOS 14+)
    if #available(iOS 14.0, *), annotation is MKUserLocation {
        
        // Try to reuse the existing view that we create below
        let reuseIdentifier = "userLocation"
        if let existingView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) {
            return existingView
        }
        let view = MKUserLocationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
        
        view.zPriority = .max   // Show user location above other annotations
        view.isEnabled = false  // Ignore touch events and do not show callout
        
        return view
    }
    
    // Create views for other annotations or return nil to use the default representation
    
    return nil
}

Note that per default, the user location annotation shows a callout when tapping on it. Now that the user location overlays your other annotations, you'd probably want to disable this, which is done in the code by setting .isEnabled to false.

3
votes

Just use the .layer.anchorPointZ property.

Example:

 func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
        views.forEach {
            if let _ = $0.annotation as? MKUserLocation {
                $0.layer.anchorPointZ = 0
            } else {
                $0.layer.anchorPointZ = 1
            }
        }
    }

Here is there reference https://developer.apple.com/documentation/quartzcore/calayer/1410796-anchorpointz

2
votes

Try, getting a reference to the user location annotation (perhaps in mapView: didAddAnnotationViews:) and then bring that view to the front of the mapView after all of your annotations have been added.

1
votes

Swift 3:

internal func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
        for annotationView in views {
            if annotationView.annotation is MKUserLocation {
                annotationView.bringSubview(toFront: view)
                return
            }
            annotationView.sendSubview(toBack: view)
        }
 }
0
votes

Here is a way to do it using predicates. I think it should be faster

NSPredicate *userLocationPredicate = [NSPredicate predicateWithFormat:@"class == %@", [MKUserLocation class]];
NSArray* userLocation = [[self.mapView annotations] filteredArrayUsingPredicate:userLocationPredicate];

if([userLocation count]) {
    NSLog(@"Bring blue location dot to front");
    MKAnnotationView *view = [self.mapView viewForAnnotation:(MKUserLocation *)[userLocation objectAtIndex:0]];
    [[view superview] bringSubviewToFront:view];
}
0
votes

Using Underscore.m

_.array(mapView.annotations).
    filter(^ BOOL (id<MKAnnotation> annotation) { return [annotation 
isKindOfClass:[MKUserLocation class]]; })
    .each(^ (id<MKAnnotation> annotation) { [[[mapView 
viewForAnnotation:annotation] superview] bringSubviewToFront:[mapView 
viewForAnnotation:annotation]]; });