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.
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.)
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"];
Définissez la propriété suivante:
animationObject.removedOnCompletion = NO;
il suffit de le mettre dans votre code
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
theGroup.fillMode = kCAFillModeForwards;
theGroup.removedOnCompletion = NO;
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)
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).
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
)
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)
}
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];
}
}
}
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)")
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é.
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"];
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];
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.
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
false
sur isRemovedOnCompletion
- laissez Core Animation propre après la fin de l'animation