web-dev-qa-db-fra.com

Comment identifier CAAnimation au sein du délégué animationDidStop?

Un problème me causait une série de séquences CATransition/CAAnimation qui se chevauchaient, toutes nécessaires pour effectuer des opérations personnalisées lorsque les animations s'arrêtaient, mais je ne voulais qu'un gestionnaire de délégués pour animationDidStop.

Cependant, j'ai eu un problème, il ne semblait pas y avoir de moyen d'identifier de manière unique chaque CATransition/CAAnimation dans le délégué animationDidStop.

J'ai résolu ce problème via le système clé/valeur exposé dans le cadre de CAAnimation.

Lorsque vous démarrez votre animation, utilisez la méthode setValue sur CATransition/CAAnimation pour définir vos identifiants et valeurs à utiliser lors du déclenchement de animationDidStop:

-(void)volumeControlFadeToOrange
{   
    CATransition* volumeControlAnimation = [CATransition animation];
    [volumeControlAnimation setType:kCATransitionFade];
    [volumeControlAnimation setSubtype:kCATransitionFromTop];
    [volumeControlAnimation setDelegate:self];
    [volumeControlLevel setBackgroundImage:[UIImage imageNamed:@"SpecialVolume1.png"] forState:UIControlStateNormal];
    volumeControlLevel.enabled = true;
    [volumeControlAnimation setDuration:0.7];
    [volumeControlAnimation setValue:@"Special1" forKey:@"MyAnimationType"];
    [[volumeControlLevel layer] addAnimation:volumeControlAnimation forKey:nil];    
}

- (void)throbUp
{
    doThrobUp = true;

    CATransition *animation = [CATransition animation]; 
    [animation setType:kCATransitionFade];
    [animation setSubtype:kCATransitionFromTop];
    [animation setDelegate:self];
    [hearingAidHalo setBackgroundImage:[UIImage imageNamed:@"m13_grayglow.png"] forState:UIControlStateNormal];
    [animation setDuration:2.0];
    [animation setValue:@"Throb" forKey:@"MyAnimationType"];
    [[hearingAidHalo layer] addAnimation:animation forKey:nil];
}

Dans votre délégué animationDidStop:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag{

    NSString* value = [theAnimation valueForKey:@"MyAnimationType"];
    if ([value isEqualToString:@"Throb"])
    {
       //... Your code here ...
       return;
    }


    if ([value isEqualToString:@"Special1"])
    {
       //... Your code here ...
       return;
    }

    //Add any future keyed animation operations when the animations are stopped.
 }

L’autre aspect de cette option est qu’il vous permet de conserver l’état dans le système d’appariement valeur-clé au lieu d’avoir à le stocker dans votre classe de délégué. Le moins de code, mieux c'est.

Veillez à consulter la référence Apple sur le codage par paire de valeurs de clé .

Existe-t-il de meilleures techniques d'identification de CAAnimation/CATransition dans le délégué animationDidStop?

Merci, -- Batgar

95
Batgar

La technique de Batgar est trop compliquée. Pourquoi ne pas tirer parti du paramètre forKey dans addAnimation? Il était destiné à cet effet même. Enlevez simplement l'appel à setValue et déplacez la chaîne de clé vers l'appel addAnimation. Par exemple:

[[hearingAidHalo layer] addAnimation:animation forKey:@"Throb"];

Ensuite, dans votre rappel animationDidStop, vous pouvez faire quelque chose comme:

if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...
87
vocaro

Je viens juste de trouver un moyen encore meilleur de créer du code de complétion pour CAAnimations:

J'ai créé un typedef pour un bloc:

typedef void (^animationCompletionBlock)(void);

Et une clé que j'utilise pour ajouter un bloc à une animation:

#define kAnimationCompletionBlock @"animationCompletionBlock"

Ensuite, si je souhaite exécuter le code de fin d'animation une fois CAAnimation terminée, je me suis défini comme délégué de l'animation et j'ai ajouté un bloc de code à l'animation à l'aide de setValue: forKey:

animationCompletionBlock theBlock = ^void(void)
{
  //Code to execute after the animation completes goes here    
};
[theAnimation setValue: theBlock forKey: kAnimationCompletionBlock];

Ensuite, j'implémente une méthode animationDidStop: terminé: qui recherche un bloc à la clé spécifiée et l'exécute si elle est trouvée:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
  animationCompletionBlock theBlock = [theAnimation valueForKey: kAnimationCompletionBlock];
  if (theBlock)
    theBlock();
}

L'intérêt de cette approche réside dans le fait que vous pouvez écrire le code de nettoyage au même endroit où vous créez l'objet d'animation. Mieux encore, le code étant un bloc, il a accès aux variables locales dans la portée englobante dans laquelle il est défini. Vous n'avez pas à vous préoccuper de la configuration de dictionnaires userInfo ou autres absurdités, ni à écrire une méthode toujours croissante d'animationDidStop: terminée: qui devient de plus en plus complexe à mesure que vous ajoutez différents types d'animations.

À vrai dire, CAAnimation devrait avoir une propriété de bloc d'achèvement intégrée et un support système pour l'appeler automatiquement, le cas échéant. Cependant, le code ci-dessus vous offre la même fonctionnalité avec seulement quelques lignes de code supplémentaire.

46
Duncan C

La seconde approche ne fonctionnera que si vous définissez explicitement votre animation sur not be supprimée à la fin avant de la lancer:

CAAnimation *anim = ...
anim.removedOnCompletion = NO;

Si vous ne le faites pas, votre animation sera supprimée avant sa fin et le rappel ne la trouvera pas dans le dictionnaire.

33
jimt

Toutes les autres réponses sont beaucoup trop compliquées! Pourquoi ne pas simplement ajouter votre propre clé pour identifier l'animation?

Cette solution est très simple. Tout ce dont vous avez besoin est de ajouter votre propre clé à l’animation (animationID dans cet exemple)

Insérer cette ligne pour identifier animation1 :

[myAnimation1 setValue:@"animation1" forKey:@"animationID"];

et ceci pour identifier animation2 :

[myAnimation2 setValue:@"animation2" forKey:@"animationID"];

Testez-le comme ceci:

- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
    if([[animation valueForKey:@"animationID"] isEqual:@"animation1"]) {
    //animation is animation1

    } else if([[animation valueForKey:@"animationID"] isEqual:@"animation2"]) {
    //animation is animation2

    } else {
    //something else
    }
}

Il ne nécessite aucune variable d'instance :

29
Tibidabo

Pour rendre explicite ce qui est supposé d'en haut (et ce qui m'a amené ici après quelques heures perdues): ne vous attendez pas à voir l'objet d'animation original que vous avez attribué vous être renvoyé par

 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag 

lorsque l'animation est terminée, car [CALayer addAnimation:forKey:] en crée une copie.

Ce sur quoi vous pouvez compter, c'est que les valeurs de clé que vous avez données à votre objet d'animation sont toujours présentes avec une valeur équivalente (mais pas nécessairement équivalente au pointeur) dans l'objet d'animation de réplica transmis avec le message animationDidStop:finished:. Comme mentionné ci-dessus, utilisez KVC et vous obtenez une marge de manœuvre suffisante pour stocker et récupérer l'état.

12
t0rst

Je peux voir la plupart du temps objc réponses que je vais faire pour Swift 2.3 sur la base de la meilleure réponse ci-dessus.

Pour commencer, il sera bon de stocker toutes ces clés sur une structure privée afin qu'elle soit dactylographiée et que sa modification ne vous apportera plus de bugs ennuyeux simplement parce que vous avez oublié de la modifier partout dans le code:

private struct AnimationKeys {
    static let animationType = "animationType"
    static let volumeControl = "volumeControl"
    static let throbUp = "throbUp"
}

Comme vous pouvez le constater, j'ai changé le nom des variables/animations afin que ce soit plus clair. Définissez maintenant ces clés lors de la création de l'animation.

volumeControlAnimation.setValue(AnimationKeys.volumeControl, forKey: AnimationKeys.animationType)

(...)

throbUpAnimation.setValue(AnimationKeys.throbUp, forKey: AnimationKeys.animationType)

Puis enfin gérer le délégué pour quand l'animation s'arrête

override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
    if let value = anim.valueForKey(AnimationKeys.animationType) as? String {
        if value == AnimationKeys.volumeControl {
            //Do volumeControl handling
        } else if value == AnimationKeys.throbUp {
            //Do throbUp handling
        }
    }
}
1
apinho

J'aime utiliser setValue:forKey: pour conserver une référence de la vue que j'anime, il est préférable de ne pas identifier l'animation de manière unique en fonction de son ID, car le même type d'animation peut être ajouté à différents calques.

Ces deux sont équivalents:

[UIView animateWithDuration: 0.35
                 animations: ^{
                     myLabel.alpha = 0;
                 } completion: ^(BOOL finished) {
                     [myLabel removeFromSuperview];
                 }];

avec celui-ci:

CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeOut.fromValue = @([myLabel.layer opacity]);
fadeOut.toValue = @(0.0);
fadeOut.duration = 0.35;
fadeOut.fillMode = kCAFillModeForwards;
[fadeOut setValue:myLabel forKey:@"item"]; // Keep a reference to myLabel
fadeOut.delegate = self;
[myLabel.layer addAnimation:fadeOut forKey:@"fadeOut"];
myLabel.layer.opacity = 0;

et dans la méthode déléguée:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    id item = [anim valueForKey:@"item"];

    if ([item isKindOfClass:[UIView class]])
    {
        // Here you can identify the view by tag, class type 
        // or simply compare it with a member object

        [(UIView *)item removeFromSuperview];
    }
}
0
Andrei Marincas

Xcode 9 Swift 4.0

Vous pouvez utiliser les valeurs de clé pour associer une animation que vous avez ajoutée à l'animation renvoyée dans la méthode de délégation animationDidStop.

Déclarez qu'un dictionnaire doit contenir toutes les animations actives et les compléments connexes:

 var animationId: Int = 1
 var animating: [Int : () -> Void] = [:]

Lorsque vous ajoutez votre animation, définissez une clé pour celle-ci:

moveAndResizeAnimation.setValue(animationId, forKey: "CompletionId")
animating[animationId] = {
    print("completion of moveAndResize animation")
}
animationId += 1    

Dans animationDidStop, la magie opère:

    let animObject = anim as NSObject
    if let keyValue = animObject.value(forKey: "CompletionId") as? Int {
        if let completion = animating.removeValue(forKey: keyValue) {
            completion()
        }
    }
0
Eng Yew

IMHO utilisant la clé-valeur d'Apple est la manière élégante de le faire: c'est spécifiquement conçu pour permettre l'ajout de données spécifiques à l'application à des objets. 

Une autre possibilité beaucoup moins élégante consiste à stocker des références à vos objets d'animation et à effectuer une comparaison du pointeur pour les identifier.

0
Teemu Kurppa

Pour moi de vérifier si 2 objets CABasicAnimation sont la même animation, J'utilise la fonction keyPath pour faire exactement cela.

if ([animationA keyPath] == [animationB keyPath])

  • Il n'est pas nécessaire de définir KeyPath pour CABasicAnimation car il n'animera plus 
0
Sirisilp Kongsilp