web-dev-qa-db-fra.com

Comment créer une animation "épinglée" personnalisée à l'aide de MKAnnotationView?

J'ai une instance de MKMapView et j'aimerais utiliser des icônes d'annotation personnalisées à la place des icônes de broches standard fournies par MKPinAnnotationView. J'ai donc configuré une sous-classe de MKAnnotationView appelée CustomMapAnnotation et surchargé -(void)drawRect: pour dessiner une image CGImage. Cela marche.

Le problème survient lorsque j'essaie de répliquer la fonctionnalité .animatesDrop fournie par MKPinAnnotationView; J'adorerais que mes icônes apparaissent progressivement, supprimées d'en haut et dans un ordre de gauche à droite, lorsque les annotations sont ajoutées à l'instance MKMapView.

Voici - (void) drawRect: for CustomMapAnnotation, qui fonctionne lorsque vous dessinez simplement le UIImage (ce que fait la deuxième ligne):

- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];
 [((Incident *)self.annotation).smallIcon drawInRect:rect];
 if (newAnnotation) {
  [self animateDrop];
  newAnnotation = NO;
 }
} 

Le problème survient lorsque vous ajoutez la méthode animateDrop:

-(void)animateDrop {
 CGContextRef myContext = UIGraphicsGetCurrentContext();

 CGPoint finalPos = self.center;
 CGPoint startPos = CGPointMake(self.center.x, self.center.y-480.0);
 self.layer.position = startPos;

 CABasicAnimation *theAnimation;
 theAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
 theAnimation.fromValue=[NSValue valueWithCGPoint:startPos];
 theAnimation.toValue=[NSValue valueWithCGPoint:finalPos];
 theAnimation.removedOnCompletion = NO;
 theAnimation.fillMode = kCAFillModeForwards;
 theAnimation.delegate = self;
 theAnimation.beginTime = 5.0 * (self.center.x/320.0);
 theAnimation.duration = 1.0;
 [self.layer addAnimation:theAnimation forKey:@""];
}

Cela ne fonctionne tout simplement pas, et il pourrait y avoir beaucoup de raisons pour cela. Je ne vais pas les aborder tous maintenant. Ce que je veux surtout savoir, c’est de savoir si l’approche est valable ou si je devrais essayer quelque chose de totalement différent.

J'ai également essayé de tout intégrer dans une transaction d'animation afin que le paramètre beginTime puisse réellement fonctionner; cela semblait ne rien faire du tout. Je ne sais pas si c'est parce qu'il me manque un élément clé ou parce que MapKit détruit mes animations d'une manière ou d'une autre.

  // Does nothing
  [CATransaction begin];
  [map addAnnotations:list];
  [CATransaction commit];

Si quelqu'un a déjà une expérience avec MKMapAnnotations animées comme celle-ci, j'aimerais bien des astuces, sinon, si vous pouvez offrir des conseils à CAAnimation sur l'approche, ce serait formidable également.

23
user226010

Un problème avec le code de Paul Shapiro est qu’il ne traite pas lorsque vous ajoutez des annotations ci-dessous où l’utilisateur regarde pour le moment. Ces annotations flotteront dans les airs avant de se déposer car elles sont déplacées dans la carte visible de l'utilisateur.

Une autre est qu'il supprime également le point bleu de l'emplacement de l'utilisateur. Avec ce code ci-dessous, vous gérez la localisation de l'utilisateur et de grandes quantités d'annotations de carte hors écran. J'ai aussi ajouté un joli rebond;)

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

    for (aV in views) {

        // Don't pin drop if annotation is user location
        if ([aV.annotation isKindOfClass:[MKUserLocation class]]) {
            continue;
        }

        // Check if current annotation is inside visible map rect, else go to next one
        MKMapPoint point =  MKMapPointForCoordinate(aV.annotation.coordinate);
        if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
            continue;
        }

        CGRect endFrame = aV.frame;

        // Move annotation out of view
        aV.frame = CGRectMake(aV.frame.Origin.x, aV.frame.Origin.y - self.view.frame.size.height, aV.frame.size.width, aV.frame.size.height);

        // Animate drop
        [UIView animateWithDuration:0.5 delay:0.04*[views indexOfObject:aV] options:UIViewAnimationCurveLinear animations:^{

            aV.frame = endFrame;

        // Animate squash
        }completion:^(BOOL finished){
            if (finished) {
                [UIView animateWithDuration:0.05 animations:^{
                    aV.transform = CGAffineTransformMakeScale(1.0, 0.8);

                }completion:^(BOOL finished){
                    [UIView animateWithDuration:0.1 animations:^{
                        aV.transform = CGAffineTransformIdentity;
                    }];
                }];
            }
        }];
    }
}
58
MrAlek

Tout d'abord, vous devez obliger votre contrôleur de vue à implémenter MKMapViewDelegate s'il ne le fait pas déjà.

Ensuite, implémentez cette méthode:

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views { 
   MKAnnotationView *aV; 
   for (aV in views) {
     CGRect endFrame = aV.frame;

     aV.frame = CGRectMake(aV.frame.Origin.x, aV.frame.Origin.y - 230.0, aV.frame.size.width, aV.frame.size.height);

     [UIView beginAnimations:nil context:NULL];
     [UIView setAnimationDuration:0.45];
     [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
         [aV setFrame:endFrame];
     [UIView commitAnimations];

   }
}

Ajoutez vos annotations à MapView et, une fois ajoutées, cette méthode de délégation sera appelée et animera les repères de haut en bas au fur et à mesure de leur ajout.

Les valeurs de synchronisation et de positionnement peuvent être légèrement modifiées, mais je l’ai peaufinée pour qu’elle ait l’air meilleur/plus proche de la chute classique (pour autant que je l’ai testée). J'espère que cela t'aides!

52
Paul Shapiro

Si vous créez une sous-classe MKAnnotationView, vous pouvez également utiliser didMoveToSuperview pour déclencher l'animation. Ce qui suit fait une goutte qui se termine par un léger effet "squish"

  #define kDropCompressAmount 0.1

  @implementation MyAnnotationViewSubclass

  ...

  - (void)didMoveToSuperview {
      CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
      animation.duration = 0.4;
      animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
      animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, -400, 0)];
      animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];

      CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform"];
      animation2.duration = 0.10;
      animation2.beginTime = animation.duration;
      animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
      animation2.toValue = [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(0, self.layer.frame.size.height*kDropCompressAmount, 0), 1.0, 1.0-kDropCompressAmount, 1.0)];
      animation2.fillMode = kCAFillModeForwards;

      CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:@"transform"];
      animation3.duration = 0.15;
      animation3.beginTime = animation.duration+animation2.duration;
      animation3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
      animation3.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
      animation3.fillMode = kCAFillModeForwards;

      CAAnimationGroup *group = [CAAnimationGroup animation];
      group.animations = [NSArray arrayWithObjects:animation, animation2, animation3, nil];
      group.duration = animation.duration+animation2.duration+animation3.duration;
      group.fillMode = kCAFillModeForwards;

      [self.layer addAnimation:group forKey:nil];
  }
12
Michael Tyson

Pour la réponse de Michael Tyson (je ne peux pas commenter pour le moment), je propose une insertion du code suivant dans didMoveToSuperview pour une réutilisation appropriée de MKAnnotationView afin qu'il répète l'animation, puis imite l'ajout séquentiel d'annotations.

Jouez avec les diviseurs et les multiplicateurs pour obtenir différents résultats visuels ...

- (void)didMoveToSuperview {
    //necessary so it doesn't add another animation when moved to superview = nil
    //and to remove the previous animations if they were not finished!
    if (!self.superview) {
        [self.layer removeAllAnimations];
        return;
    }


    float xOriginDivider = 20.;
    float pos = 0;

    UIView *mySuperview = self.superview;
    while (mySuperview && ![mySuperview isKindOfClass:[MKMapView class]])
        mySuperview = mySuperview.superview;
    if ([mySuperview isKindOfClass:[MKMapView class]]) 
        //given the position in the array
        // pos = [((MKMapView *) mySuperview).annotations indexOfObject:self.annotation];   
        // left to right sequence;
        pos = [((MKMapView *) mySuperview) convertCoordinate:self.annotation.coordinate toPointToView:mySuperview].x / xOriginDivider;

    float yOffsetMultiplier = 20.;
    float timeOffsetMultiplier = 0.05;


    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
    animation.duration = 0.4 + timeOffsetMultiplier * pos;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, -400 - yOffsetMultiplier * pos, 0)];
    animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];

    // rest of animation group...
}
5
tsakoyan

Je pense que la meilleure option est de définir "animatesDrop" sur YES. C'est une propriété de MKPinAnnotationView.

0
Chris