web-dev-qa-db-fra.com

CABasicAnimation revient à la valeur initiale une fois l'animation terminée

Je fais pivoter une couche CALayer et j'essaie de l'arrêter à sa position finale une fois l'animation terminée.

Mais une fois l'animation terminée, sa position initiale est réinitialisée.

(La documentation xcode indique explicitement que l'animation ne mettra pas à jour la valeur de la propriété.)

des suggestions comment y parvenir.

116
Nilesh Ukey

Edit : Voici la réponse, c'est une combinaison de ma réponse et celle de Krishnan.

cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;

La valeur par défaut est kCAFillModeRemoved. (Quel est le comportement de réinitialisation que vous voyez.)

243
Nilesh Ukey

Le problème avec removedOnCompletion est que l'élément d'interface utilisateur n'autorise pas l'interaction de l'utilisateur.

I technique consiste à définir la valeur FROM dans l'animation et la valeur TO sur l'objet . L'animation remplira automatiquement la valeur TO avant de commencer et, une fois supprimée, laissera l'objet à l'état correct.

// fade in
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"];
alphaAnimation.fillMode = kCAFillModeForwards;

alphaAnimation.fromValue = NUM_FLOAT(0);
self.view.layer.opacity = 1;

[self.view.layer addAnimation: alphaAnimation forKey: @"fade"];
70
Leslie Godwin

Définissez la propriété suivante:

animationObject.removedOnCompletion = NO;
16
Krishnan

il suffit de le mettre dans votre code

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;
11
msk_sureshkumar

Vous pouvez simplement définir la clé CABasicAnimation sur position lorsque vous l'ajoutez à la couche. En faisant cela, il remplacera l'animation implicite effectuée sur la position du passage actuel dans la boucle d'exécution.

CGFloat yOffset = 30;
CGPoint endPosition = CGPointMake(someLayer.position.x,someLayer.position.y + yOffset);

someLayer.position = endPosition; // Implicit animation for position

CABasicAnimation * animation =[CABasicAnimation animationWithKeyPath:@"position.y"]; 

animation.fromValue = @(someLayer.position.y);
animation.toValue = @(someLayer.position.y + yOffset);

[someLayer addAnimation:animation forKey:@"position"]; // The explicit animation 'animation' override implicit animation

Vous pouvez avoir plus d'informations sur Apple WWDC Video Session 421 - Core Animation Essentials 2011 (au milieu de la vidéo)

9
yageek

Régler simplement fillMode et removedOnCompletion n'a pas fonctionné pour moi. J'ai résolu le problème en définissant all des propriétés ci-dessous sur l'objet CABasicAnimation:

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.duration = 0.38f;
ba.fillMode = kCAFillModeForwards;
ba.removedOnCompletion = NO;
ba.autoreverses = NO;
ba.repeatCount = 0;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.85f, 0.85f, 1.0f)];
[myView.layer addAnimation:ba forKey:nil];

Ce code transforme myView à 85% de sa taille (3ème dimension non modifiée).

6

Une couche CAL a une couche modèle et une couche présentation. Pendant une animation, la couche de présentation est mise à jour indépendamment du modèle. Une fois l'animation terminée, la couche de présentation est mise à jour avec la valeur du modèle. Si vous souhaitez éviter un saut saccadé à la fin de l'animation, il est essentiel de synchroniser les deux couches.

Si vous connaissez la valeur finale, vous pouvez simplement définir le modèle directement.

self.view.layer.opacity = 1;

Mais si vous avez une animation dont vous ne connaissez pas la position finale (par exemple, un fondu lent que l'utilisateur peut suspendre puis inverser), vous pouvez interroger directement la couche de présentation pour rechercher la valeur actuelle, puis mettre à jour le modèle.

NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:@"opacity"];
[self.layer setValue:opacity forKeyPath:@"opacity"];

Extraire la valeur de la couche de présentation est également particulièrement utile pour les chemins de clé de redimensionnement ou de rotation. (par exemple, transform.scale, transform.rotation)

4
Jason Moore

Sans utiliser la removedOnCompletion

Vous pouvez essayer cette technique:

self.animateOnX(item: shapeLayer)

func animateOnX(item:CAShapeLayer)
{
    let endPostion = CGPoint(x: 200, y: 0)
    let pathAnimation = CABasicAnimation(keyPath: "position")
    //
    pathAnimation.duration = 20
    pathAnimation.fromValue = CGPoint(x: 0, y: 0)//comment this line and notice the difference
    pathAnimation.toValue =  endPostion
    pathAnimation.fillMode = kCAFillModeBoth

    item.position = endPostion//prevent the CABasicAnimation from resetting item's position when the animation finishes

    item.add(pathAnimation, forKey: nil)
}
4
Ayman Ibrahim

La réponse de @Leslie Godwin n'est pas vraiment bonne, "self.view.layer.opacity = 1;" est fait immédiatement (cela prend environ une seconde), veuillez fixer alphaAnimation.duration à 10.0, si vous avez des doutes . Vous devez supprimer cette ligne.

Ainsi, lorsque vous corrigez fillMode sur kCAFillModeForwards et removeOnCompletion sur NO, vous laissez l'animation rester dans la couche. Si vous corrigez le délégué d'animation et essayez quelque chose comme:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
 [theLayer removeAllAnimations];
}

... la couche est restaurée immédiatement au moment où vous exécutez cette ligne. C'est ce que nous voulions éviter. 

Vous devez corriger la propriété layer avant de supprimer l'animation. Essaye ça:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
     if([anim isKindOfClass:[CABasicAnimation class] ]) // check, because of the cast
    {
        CALayer *theLayer = 0;
        if(anim==[_b1 animationForKey:@"opacity"])
            theLayer = _b1; // I have two layers
        else
        if(anim==[_b2 animationForKey:@"opacity"])
            theLayer = _b2;

        if(theLayer)
        {
            CGFloat toValue = [((CABasicAnimation*)anim).toValue floatValue];
            [theLayer setOpacity:toValue];

            [theLayer removeAllAnimations];
        }
    }
}
4
tontonCD

Donc, mon problème était que j'essayais de faire pivoter un objet sur un geste panoramique et que j'avais donc plusieurs animations identiques à chaque mouvement. J'ai eu à la fois fillMode = kCAFillModeForwards et isRemovedOnCompletion = false mais cela n'a pas aidé. Dans mon cas, je devais m'assurer que la clé d'animation est différente chaque fois que j'ajoute une nouvelle animation :

let angle = // here is my computed angle
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.toValue = angle
rotate.duration = 0.1
rotate.isRemovedOnCompletion = false
rotate.fillMode = kCAFillModeForwards

head.layer.add(rotate, forKey: "rotate\(angle)")
2
sunshinejr

Cela marche:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3

someLayer.opacity = 1 // important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

L'animation principale montre une "couche de présentation" au-dessus de votre couche normale pendant l'animation. Réglez donc l'opacité (ou autre chose) sur ce que vous voulez voir à la fin de l'animation et lorsque la couche de présentation s'en va. Faites ceci sur la ligne avant vous ajoutez l'animation pour éviter un scintillement à la fin.

Si vous souhaitez avoir un délai, procédez comme suit:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3
animation.beginTime = someLayer.convertTime(CACurrentMediaTime(), fromLayer: nil) + 1
animation.fillMode = kCAFillModeBackwards // So the opacity is 0 while the animation waits to start.

someLayer.opacity = 1 // <- important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

Enfin, si vous utilisez "removedOnCompletion = false", CAAnimations sera filtré jusqu'à ce que le calque soit finalement supprimé.

2
Chris

Il semble que les indicateurs removeOnCompletion définis sur false et que fillMode défini sur kCAFillModeForwards ne fonctionne pas pour moi non plus. 

Une fois que j'ai appliqué une nouvelle animation sur un calque, un objet animé revient à son état initial, puis s'anime à partir de cet état . animation comme celle-ci:

someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];
1

L'animation principale maintient deux hiérarchies de couche: la couche modèle et la couche présentation . Lorsque l'animation est en cours, la couche du modèle est en fait intacte et conserve sa valeur initiale. Par défaut, l'animation est supprimée une fois terminée. Ensuite, la couche de présentation revient à la valeur de la couche de modèle.

Régler simplement removedOnCompletion sur NO signifie que l'animation ne sera pas supprimée et gaspille de la mémoire. De plus, la couche modèle et la couche présentation ne seront plus synchrones, ce qui peut générer des bogues.

Il serait donc préférable de mettre à jour la propriété directement sur la couche du modèle à la valeur finale.

self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

S'il y a une animation implicite causée par la première ligne du code ci-dessus, essayez de désactiver si:

[CATransaction begin];
[CATransaction setDisableActions:YES];

self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

[CATransaction commit];
1
Lizhen Hu

La solution la plus simple consiste à utiliser des animations implicites. Cela va gérer tout ce problème pour vous:

self.layer?.backgroundColor = NSColor.red.cgColor;

Si vous souhaitez personnaliser, par exemple, la durée, vous pouvez utiliser NSAnimationContext:

    NSAnimationContext.beginGrouping();
    NSAnimationContext.current.duration = 0.5;
    self.layer?.backgroundColor = NSColor.red.cgColor;
    NSAnimationContext.endGrouping();

Remarque: Ceci est uniquement testé sur macOS.

Au début, je n'ai vu aucune animation lors de cette opération. Le problème est que le calque d'un calque avec affichage est pas implicite animé. Pour résoudre ce problème, veillez à ajouter vous-même un calque (avant de définir la vue sur le calque).

Un exemple comment faire cela serait:

override func awakeFromNib() {
    self.layer = CALayer();
    //self.wantsLayer = true;
}

L’utilisation de self.wantsLayer n’a fait aucune différence dans mes tests, mais cela pourrait avoir des effets secondaires que je ne connaissais pas.

0
paxos

Voici un échantillon de terrain de jeu:

import PlaygroundSupport
import UIKit

let resultRotation = CGFloat.pi / 2
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 300.0))
view.backgroundColor = .red

//--------------------------------------------------------------------------------

let rotate = CABasicAnimation(keyPath: "transform.rotation.z") // 1
rotate.fromValue = CGFloat.pi / 3 // 2
rotate.toValue = resultRotation // 3
rotate.duration = 5.0 // 4
rotate.beginTime = CACurrentMediaTime() + 1.0 // 5
// rotate.isRemovedOnCompletion = false // 6
rotate.fillMode = .backwards // 7

view.layer.add(rotate, forKey: nil) // 8
view.layer.setAffineTransform(CGAffineTransform(rotationAngle: resultRotation)) // 9

//--------------------------------------------------------------------------------

PlaygroundPage.current.liveView = view
  1. Créer un modèle d'animation
  2. Définir la position de départ de l'animation (peut être ignoré, cela dépend de la disposition actuelle de votre calque)
  3. Définir la position de fin de l'animation
  4. Définir la durée de l'animation
  5. Retarder l'animation d'une seconde
  6. Ne définissez pas false sur isRemovedOnCompletion - laissez Core Animation propre après la fin de l'animation
  7. Voici la première partie de l'astuce - vous dites à Core Animation de placer votre animation à la position de départ (définie à l'étape 2) avant la l'animation a même été lancée - prolongez-la dans le temps
  8. Copiez l'objet d'animation préparé, ajoutez-le au calque et démarrez l'animation après le délai (défini à l'étape 5).
  9. La deuxième partie consiste à définir la position finale correcte du calque - après la suppression de l'animation, votre calque sera affiché à l'écran. endroit approprié
0
adnako