How do I animate MKAnnotationView drop?

Viewed 18034

I have a custom MKAnnotationView where I set my image myself in viewForAnnotation. How do I animate it's drop like I can with MKPinAnnotationView?

My code is

- (MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation
{
    static NSString *AnnotationViewID = @"annotationViewID";

    MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];

    if (annotationView == nil)
    {
        annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID] autorelease];
    }

    annotationView.image = [UIImage imageNamed:@"blah.png"];
    annotationView.annotation = annotation;

    return annotationView;
}
7 Answers

Updated for Swift 4.2

   func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
    var i = -1;
    for view in views {
        i += 1;
        if view.annotation is MKUserLocation {
            continue;
        }
        let point:MKMapPoint  =  MKMapPoint(view.annotation!.coordinate);
        if (!self.mapView.visibleMapRect.contains(point)) {
            continue;
        }
        let endFrame:CGRect = view.frame;
        view.frame = CGRect(origin: CGPoint(x: view.frame.origin.x,y :view.frame.origin.y-self.view.frame.size.height), size: CGSize(width: view.frame.size.width, height: view.frame.size.height))
        let delay = 0.03 * Double(i)
        UIView.animate(withDuration: 0.5, delay: delay, options: UIView.AnimationOptions.curveEaseIn, animations:{() in
            view.frame = endFrame
        }, completion:{(Bool) in
            UIView.animate(withDuration: 0.05, delay: 0.0, options: UIView.AnimationOptions.curveEaseInOut, animations:{() in
                view.transform = CGAffineTransform(scaleX: 1.0, y: 0.6)
            }, completion: {(Bool) in
                UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions.curveEaseInOut, animations:{() in
                    view.transform = CGAffineTransform.identity
                }, completion: nil)
            })
        })
    }
}

Rather than implementing mapView(_:didAdd:) in the MKMapViewDelegate, you can also have the annotation view do the animation itself.

class CustomAnnotationView: MKAnnotationView {
    override var annotation: MKAnnotation? { didSet { update(for: annotation) } }

    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
        update(for: annotation)
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        removeFromSuperview()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func didMoveToSuperview() {
        super.didMoveToSuperview()

        transform = CGAffineTransform(translationX: 0, y: -100)
        alpha = 0
        UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.4) {
            self.transform = .identity
            self.alpha = 1
        }.startAnimation()
    }

    private func update(for annotation: MKAnnotation?) {
        // do whatever update to the annotation view you want, if any
    }
}

This is useful for avoiding the cluttering of one’s view controller with annotation view animations. E.g. in iOS 11 and later, you might do:

mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)

And then you can add an annotation to your map view, and you get this animation of the annotation view without any more code in the view controller.

This particular animation is a drop with a little bounce at the end, but obviously you can do whatever animation you want.

The above was written in Swift, but the concept is equally valid in Objective-C, too.

Related