CALayerを回転させて、アニメーションの完了後に最終位置で停止しようとしています。
ただし、アニメーションが完了すると、初期位置にリセットされます。
(xcodeドキュメントは、アニメーションがプロパティの値を更新しないと明示的に述べています。)
これを達成する方法の提案。
編集:ここに答えがあります。それは私の答えとクリシュナンの組み合わせです。
cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;
デフォルト値はkCAFillModeRemoved
です。 (表示されているリセット動作です。)
RemovedOnCompletionの問題は、UI要素がユーザーの操作を許可しないことです。
テクニックは、アニメーションのFROM値とオブジェクトのTO値を設定することです。アニメーションは、開始前にTO値を自動的に入力し、削除されるとオブジェクトを正しい状態のままにします。
// 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"];
次のプロパティを設定します。
animationObject.removedOnCompletion = NO;
コードの中に置くだけです
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
theGroup.fillMode = kCAFillModeForwards;
theGroup.removedOnCompletion = NO;
レイヤーに追加するときに、単にCABasicAnimation
のキーをposition
に設定できます。これにより、実行ループ内の現在のパスの位置で行われた暗黙のアニメーションがオーバーライドされます。
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
2011 Apple WWDC Video Session 421-Core Animation Essentials(ビデオの中央)
CALayerには、モデルレイヤーとプレゼンテーションレイヤーがあります。アニメーション中、プレゼンテーション層はモデルとは独立して更新されます。アニメーションが完了すると、プレゼンテーションレイヤーがモデルの値で更新されます。アニメーションの終了後、不快なジャンプを避けたい場合は、2つのレイヤーの同期を保つことが重要です。
最終値がわかっている場合は、モデルを直接設定できます。
self.view.layer.opacity = 1;
ただし、終了位置がわからないアニメーション(ユーザーが一時停止してから元に戻すことができるスローフェードなど)がある場合は、プレゼンテーションレイヤーを直接クエリして現在の値を見つけ、モデルを更新できます。
NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:@"opacity"];
[self.layer setValue:opacity forKeyPath:@"opacity"];
プレゼンテーションレイヤーから値を取得することも、スケーリングまたは回転キーパスに特に役立ちます。 (例:transform.scale
、transform.rotation
)
単にfillMode
とremovedOnCompletion
を設定してもうまくいきませんでした。以下のプロパティのallを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];
このコードは、myView
をそのサイズの85%に変換します(3次元は変更されません)。
removedOnCompletion
を使用せずに
このテクニックを試すことができます:
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)
}
これは動作します:
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")
コアアニメーションは、アニメーション中に通常のレイヤーの上に「プレゼンテーションレイヤー」を表示します。そのため、アニメーションが終了してプレゼンテーションレイヤーが消えたときに表示されるように、不透明度(または何でも)を設定します。行でこれを行いますbeforeアニメーションを追加して、完了時のちらつきを防ぎます。
遅延が必要な場合は、次を実行します。
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")
最後に、「removedOnCompletion = false」を使用すると、レイヤーが最終的に破棄されるまでCAAnimationsがリークします。
@Leslie Godwinの答えは本当に良くありません、「self.view.layer.opacity = 1;」すぐに完了します(約1秒かかります)。疑問がある場合は、alphaAnimation.durationを10.0に修正してください。この行を削除する必要があります。
そのため、fillModeをkCAFillModeForwardsに修正し、removeOnCompletionをNOに修正すると、アニメーションはレイヤーに残ります。アニメーションデリゲートを修正して、次のようなことを試してください。
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
[theLayer removeAllAnimations];
}
...この行を実行すると、レイヤーはすぐに復元されます。それは避けたいものです。
アニメーションを削除する前に、レイヤープロパティを修正する必要があります。これを試して:
- (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];
}
}
}
したがって、私の問題は、パンジェスチャでオブジェクトを回転させようとしていたため、各移動で複数の同一のアニメーションがあったことです。私は両方を持っていたfillMode = kCAFillModeForwards
およびisRemovedOnCompletion = false
しかし、それは助けにはなりませんでした。私の場合、新しいアニメーションを追加するたびにアニメーションキーが異なることを確認する必要がありました:
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つのレイヤー階層 モデルレイヤー および プレゼンテーションレイヤー を維持します。アニメーションの進行中、モデルレイヤーは実際にはそのままで、初期値を保持します。デフォルトでは、アニメーションは完了すると削除されます。次に、プレゼンテーションレイヤーはモデルレイヤーの値にフォールバックします。
removedOnCompletion
をNO
に設定するだけで、アニメーションが削除されず、メモリが浪費されることを意味します。さらに、モデルレイヤーとプレゼンテーションレイヤーは同期しなくなるため、潜在的なバグが発生する可能性があります。
そのため、モデルレイヤーのプロパティを直接最終値に更新する方が適切なソリューションになります。
self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];
上記のコードの最初の行によって暗黙的なアニメーションが発生している場合は、オフにします:
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.view.layer.opacity = 1;
[CATransaction commit];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];
RemovedOnCompletionフラグがfalseに設定され、fillModeがkCAFillModeForwardsに設定されていることも、私にとっては機能しないようです。
レイヤーに新しいアニメーションを適用すると、アニメーション化オブジェクトが初期状態にリセットされ、その状態からアニメーション化されます。さらに、次のように新しいアニメーションを設定する前に、プレゼンテーションレイヤーのプロパティに従ってモデルレイヤーの目的のプロパティを設定する必要があります。
someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];
最も簡単な解決策は、暗黙的なアニメーションを使用することです。これにより、すべての問題が処理されます。
self.layer?.backgroundColor = NSColor.red.cgColor;
カスタマイズしたい場合期間、NSAnimationContext
を使用できます:
NSAnimationContext.beginGrouping();
NSAnimationContext.current.duration = 0.5;
self.layer?.backgroundColor = NSColor.red.cgColor;
NSAnimationContext.endGrouping();
注:これはmacOSでのみテストされています。
これを行うときに、最初はアニメーションが表示されませんでした。問題は、view-backedレイヤーのレイヤーがnot暗黙のアニメーションを行うことです。これを解決するには、必ず自分でレイヤーを追加してください(ビューをレイヤーに設定する前に)。
これを行う方法の例は次のとおりです。
override func awakeFromNib() {
self.layer = CALayer();
//self.wantsLayer = true;
}
self.wantsLayer
を使用してもテストに違いはありませんでしたが、わからない副作用が発生する可能性があります。
遊び場のサンプルを次に示します。
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
をisRemovedOnCompletion
に設定しないでください-アニメーションが終了した後、Core Animationを消去してください