13

I have a map and a set of annotations, each with a 'parent' property. Currently when I add annotations I implement the didAddAnnotationViews method to animate those annotations so they appear to come from their parent's coordinate. Is there a way of doing this during the removal of annotations? When I remove an annotation from the map I want it to animate in to its parent coordinate, and as far as I know there is no equivalent for didAddAnnotationViews for when an annotation is removed.

benwad
  • 6,414
  • 10
  • 59
  • 93

2 Answers2

16

Animate annotation before you remove it from the map and perform removal after animation is completed. The code may look like:

- (void) removeMyAnnotation:(MyAnnotation*)annotation{
   [UIView animateWithDuration:1.0f
                    animations:^(void){
                         annotation.coordinate = annotation.parentAnnotation.coordinate;
                    }
                    completion:^(BOOL finished)completion{
                        [mapView removeAnnotation:annotation];
                    }
}
Vladimir
  • 170,431
  • 36
  • 387
  • 313
  • Thanks - one other thing, is there a way of accessing the annotation's corresponding MKAnnotationView so I can move it around that way? Because my annotation class calculates its coordinate dynamically so it cannot be set (unless I add in a load of functionality for setting custom coordinates, which I'd rather avoid). – benwad Dec 20 '11 at 10:14
  • 2
    @benwad, you can get it using viewForAnnotation method in MKMapView, although it is not guaranteed to always return actual view (it can return nil if annotation is not visible for example) – Vladimir Dec 20 '11 at 10:20
  • @indiekiduk, check the question - in its conditions each annotation has parentAnnotation as a property – Vladimir Jul 11 '13 at 08:38
  • hey Vladimir i need your help look at this question http://stackoverflow.com/q/19268080/1468406 please help if you have any idea. – Pratik B Oct 15 '13 at 06:19
5

You should NOT defer call of removeAnnotation like in @Vladimir's answer because state of MKMapView could be changed during the animation.

By the time the removeAnnotation is called from animation completion block, another annotations could be added / removed from the MapView - so, in some cases you can end up removing wrong set of annotations.

I wrote this category for MKMapView you can use for animated annotation removal in safe manner:

@interface MKMapView (RemoveAnnotationWithAnimation)

- (void)removeAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated;
- (void)removeAnnotations:(NSArray *)annotations animated:(BOOL)animated;

@end

And the .m file:

@implementation MKMapView (RemoveAnnotationWithAnimation)

- (void)removeAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated
{
    [self removeAnnotations:@[annotation] animated:animated];
}

- (void)removeAnnotations:(NSArray *)annotations animated:(BOOL)animated
{
    if (animated) {
        NSSet * visibleAnnotations = [self annotationsInMapRect:self.visibleMapRect];
        NSMutableArray * annotationsToRemoveWithAnimation = [NSMutableArray array];
        for (id<MKAnnotation> annotation in annotations) {
            if ([visibleAnnotations containsObject:annotation]) {
                [annotationsToRemoveWithAnimation addObject:annotation];
            }
        }
        NSMutableArray * snapshotViews = [NSMutableArray array];
        for (id<MKAnnotation> annotation in annotationsToRemoveWithAnimation) {
            UIView * annotationView = [self viewForAnnotation:annotation];
            if (annotationView) {
                UIView * snapshotView = [annotationView snapshotViewAfterScreenUpdates:NO];
                snapshotView.frame = annotationView.frame;
                [snapshotViews addObject:snapshotView];
                [[annotationView superview] insertSubview:snapshotView aboveSubview:annotationView];
            }
        }
        [UIView animateWithDuration:0.5
                         animations:^{
                             for (UIView * snapshotView in snapshotViews) {
                                 // Change the way views are animated if you want
                                 CGRect frame = snapshotView.frame;
                                 frame.origin.y = -frame.size.height;
                                 snapshotView.frame = frame;
                             }
                         }
                         completion:^(BOOL finished) {
                             for (UIView * snapshotView in snapshotViews) {
                                 [snapshotView removeFromSuperview];
                             }
                         }];
    }
    [self removeAnnotations:annotations];
}

@end
nalexn
  • 10,615
  • 6
  • 44
  • 48
  • I implemented this solution for fading my annotations out. This doesn't work when panning the map while the snapshot views are fading out, as they will stay put, while the map moves. Looks pretty strange. – duncanc4 Jan 13 '16 at 01:56
  • After reading over your comment and thinking about @Vladimir's solution, I don't see how adding or removing different annotations during the animation effects the ability to remove the specific annotation when the completion block executes. The block captures the pointer, so it has the exact annotation to remove. The only problem I could see is if some other code removed the exact annotation that was animating. I'm not for sure what `MKMapView` does if it doesn't have the annotation you are trying to remove. – duncanc4 Jan 13 '16 at 02:12
  • @duncanc4 The problem is that MKMapView reuses annotation view instances, like UITableView does with its cells. So if you store a pointer to an annotation being removed and scroll the map to other group of annotations, you can end up removing wrong annotation when the block executes. – nalexn Jan 13 '16 at 04:50
  • If you store the MKAnnotationView that would happen, but how does it happen if you store the MKAnnotation, as MKMapView doesn't reuse MKAnnotations? – duncanc4 Jan 13 '16 at 07:01
  • Yep, correct. I remember I had some issues with Vladimir's solution, and had to come up with my own. I don't remember all the details unfortunately. Just in general, I still think that update of the Presentation should be done in conjunction with update of the Model. When time is involved and you delay updates of the Model, beware unexpected state of the Presentation. – nalexn Jan 13 '16 at 10:36
  • This is an old thread, but I'll chime in anyway. An annotation object is any object that conforms to the MKAnnotation protocol. It's a model object, and is not reused. Changing the coordinate of an annotation object should not cause a problem with view reuse. If you remove an annotation then the map will look to see if that annotation has a view on-screen and remove it if so, and not change the map's views if that annotation's view is not on-screen. @duncanc4, greetings to another DuncanC! There are few Duncan's here, but you are the only other DuncanC I've seen! – Duncan C May 20 '17 at 18:54