アニメーションの完了後、CABasicAnimationが初期値にリセットされます


143

アニメーションが完了した後、CALayerを回転させて、最終位置で停止しようとしています。

ただし、アニメーションが完了すると、初期位置にリセットされます。

(xcodeドキュメントは、アニメーションがプロパティの値を更新しないことを明示的に言っています。)

これを達成する方法の提案。


1
これは、ほとんどすべての回答がまったく間違っている奇妙なSO質問の1つです-完全に間違っています。あなたは非常に単純に見て.presentation()、「最終的な、見られた」値を得ます。プレゼンテーション層で行われたことを説明する以下の正しい答えを探してください。
Fattie

回答:


285

これが答えです。私の答えとクリシュナンの組み合わせです。

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

デフォルト値はkCAFillModeRemovedです。(これは、表示されているリセット動作です。)


6
これはおそらく正しい答えではありません-クリシュナンの答えに関するコメントを参照してください。通常、これとクリシュナンが必要です。どちらか一方だけでは機能しません。
アダム

19
これは正しい答えではありません。以下の@Leslie Godwinの答えを参照してください。
Mark Ingram

6
@Leslieソリューションは、最終値をアニメーションではなくレイヤーに格納するため、より優れています。
Matthieu Rouif、2014

1
deletedOnCompletionをNOに設定するときは注意してください。アニメーションデリゲートを「self」に割り当てる場合、インスタンスはアニメーションによって保持されます。したがって、removeFromSuperviewを呼び出した後、アニメーションを自分で削除または破棄することを忘れた場合、たとえばdeallocは呼び出されません。メモリリークが発生します。
emrahgunduz、2015年

1
この200ポイントを超える回答は、完全に、完全に、まったく正しくありません。
Fattie

83

deletedOnCompletionの問題は、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"];

7
開始遅延を設定すると機能しません。例:alphaAnimation.beginTime = 1; また、fillMode私が知る限り、これは必要ありません...?
サム・

それ以外の場合、これは良い答えです。承認された答えでは、アニメーションの完了後に値を変更できません...
Sam

2
私もそれは注意しなければならないbeginTime相対的ではありません、あなたが使用する必要があります。例:CACurrentMediaTime()+1;
サム

それは「it's」ではなく「its」です-its-not-its.info。それが単なるタイプミスだったら申し訳ありません。
fatuhoku 2014


16

次のプロパティを設定します。

animationObject.removedOnCompletion = NO;

@Nilesh:答えを受け入れてください。それは他のSOユーザーにあなたの答えに自信を与えるでしょう。
クリシュナン

7
.fillMode = kCAFillModeForwardsと.removedOnCompletion = NOの両方を使用する必要がありました。ありがとう!
William Rust 2011年

12

あなたのコードの中に入れてください

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;

12

レイヤーに追加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(ビデオの中央)に関する詳細情報があります。


1
これはこれを行う正しい方法のように見えます。アニメーションは自然に削除され(デフォルト)、アニメーションが完了すると、アニメーションが開始する前に設定されていた値、特に次の行に戻りますsomeLayer.position = endPosition;。そして、WWDCへの参照をありがとう!
bauerMusic 2016年

10

CALayerには、モデルレイヤーとプレゼンテーションレイヤーがあります。アニメーション中、プレゼンテーションレイヤーはモデルとは関係なく更新されます。アニメーションが完了すると、プレゼンテーションレイヤーがモデルの値で更新されます。アニメーションが終了した後に不快なジャンプを避けたい場合は、2つのレイヤーの同期を保つことが重要です。

最終値がわかっている場合は、モデルを直接設定できます。

self.view.layer.opacity = 1;

ただし、終了位置がわからないアニメーション(たとえば、ユーザーが一時停止してから戻すことができるゆっくりとしたフェード)がある場合は、プレゼンテーションレイヤーに直接クエリを実行して現在の値を確認し、モデルを更新できます。

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

プレゼンテーションレイヤーから値を取得することは、キーパスのスケーリングや回転にも特に役立ちます。(例transform.scaletransform.rotation


8

を使用せずに 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)
}

3
これは最良の機能の答えのようです
rambossa '22

同意した。.fromValueプロパティを開始値に定義する必要があるという@aymanのコメントに注意してください。そうしないと、モデルの開始値が上書きされ、アニメーションが発生します。
ジェフコリアー

これと@ jason-mooreの回答は、受け入れられた回答よりも優れています。受け入れられた回答では、アニメーションは一時停止されていますが、新しい値は実際にはプロパティに設定されていません。これにより、望ましくない動作が発生する可能性があります。
laka

8

したがって、私の問題は、パンジェスチャーでオブジェクトを回転させようとしていたことで、1回の移動で複数の同一のアニメーションが発生しました。私は両方を持っていた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 = CAMediaTimingFillMode.forwards

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

7

単に設定しfillModeremovedOnCompletion私のために動作しませんでした。以下のすべてのプロパティを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次元は変更されません)。


7

コアアニメーションは、モデルレイヤープレゼンテーションレイヤーという 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];

ありがとうございました!それは、正しくそれを動作させるためだけにバンドエイドを使用する代わりに、この機能を説明しているようにこれは本当に、受け入れ答えなければなりません
bplattenburg

6

これは機能します:

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")

コアアニメーションは、アニメーション中に通常のレイヤーの上に「プレゼンテーションレイヤー」を表示します。そのため、アニメーションが終了してプレゼンテーションレイヤーが消えたときに表示する不透明度(または何でも)を設定します。アニメーションを追加するに、この行でこれを実行して、完了時にちらつきを回避します。

遅延させたい場合は、次のようにします。

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がリークされます-回避してください。


すばらしい答え、すばらしいヒント。deletedOnCompletionコメントをお寄せいただきありがとうございます。
iWheelBuy

4

@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];
        }
    }
}

1

また、removeOnCompletionフラグをfalseに設定し、fillModeをkCAFillModeForwardsに設定しても機能しません。

レイヤーに新しいアニメーションを適用すると、アニメーションオブジェクトが初期状態にリセットされ、その状態からアニメーションします。さらに行う必要があるのは、次のように新しいアニメーションを設定する前に、プレゼンテーションレイヤーのプロパティに従ってモデルレイヤーの必要なプロパティを設定することです。

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

0

最も簡単な解決策は、暗黙のアニメーションを使用することです。これはあなたのためにそのトラブルのすべてを処理します:

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

たとえば期間をカスタマイズしたい場合は、以下を使用できますNSAnimationContext

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

注:これはmacOSでのみテストされています。

これを行うと、最初はアニメーションが表示されませんでした。問題は、ビュー付きレイヤーのレイヤーが暗黙的にアニメーション化しないことです。これを解決するには、自分でレイヤーを追加してください(ビューをレイヤーバッキングに設定する前に)。

これを行う方法の例は次のとおりです。

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

を使用self.wantsLayerしてもテストに違いはありませんでしたが、知らない副作用が発生する可能性があります。


0

遊び場からのサンプルです:

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. アニメーションモデルを作成する
  2. アニメーションの開始位置設定します(スキップできます。現在のレイヤーレイアウトによって異なります)
  3. アニメーションの終了位置設定します
  4. アニメーション期間を設定する
  5. アニメーションを1秒間遅らせる
  6. 設定しないでくださいfalseisRemovedOnCompletion-アニメーションが終了した後のCore Animationがきれいましょう
  7. ここでのトリックの最初の部分は、 -時間に逆方向にそれを拡張-あなたはスタート位置にアニメーションを配置するためのCore Animationに言うアニメーションも開始されている前に(あなたがステップ#2で設定しました)
  8. 準備されたアニメーションオブジェクトをコピーしてレイヤーに追加し、遅延後にアニメーションを開始します(手順5で設定)
  9. 2番目の部分、レイヤーの正しい終了位置を設定することです-アニメーションが削除された後、レイヤーは正しい場所に表示されます
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.