animationDidStopデリゲート内のCAAnimationを識別する方法は?


102

一連のCATransition / CAAnimationシーケンスが重複していて、アニメーションが停止したときにカスタム操作を実行する必要があるという問題がありましたが、animationDidStopのデリゲートハンドラは1つしか必要ではありませんでした。

ただし、問題がありました。animationDidStopデリゲート内の各CATransition / CAAnimationを一意に識別する方法がないようです。

CAAnimationの一部として公開されているキー/値システムを使用して、この問題を解決しました。

アニメーションを開始するときは、CATransition / CAAnimationのsetValueメソッドを使用して、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];
}

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.
 }

これのもう1つの側面は、デリゲートクラスに格納する代わりに、キーと値のペアリングシステムで状態を維持できることです。コードが少ないほど良いです。

キーと値のペアのコーディングに関するAppleリファレンスを必ずチェックしてください。

animationDidStopデリゲートでCAAnimation / CATransitionを識別するためのより良いテクニックはありますか?

ありがとう、-バトガー


4
Batgar、「iphone animationDidStop識別」を検索したところ、最初のヒットはあなたの投稿で、アニメーションを識別するためにKey-Valueを使用することを示唆していました。ちょうど私が必要としたもの、ありがとう。ルディ
ルディファ2009

1
CAAnimation's delegateは強いので、nil保持サイクルを回避するように設定する必要があるかもしれないことに注意してください!
Iulian Onofrei 16

回答:


92

Batgarのテクニックは複雑すぎます。addAnimationのforKeyパラメーターを利用しないのはなぜですか?これはまさにこの目的のためのものでした。setValueの呼び出しを取り出し、キー文字列をaddAnimation呼び出しに移動するだけです。例えば:

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

次に、animationDidStopコールバックで、次のようなことができます。

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

上記のINCREMENTS THE RETAIN COUNTを使用することを述べたいと思います!警告されます。つまり、animationForKey:CAAnimationオブジェクトの保持カウントを増分します。
mmilo

1
@mmiloそれはそれほど驚くべきことではありませんか?レイヤーにアニメーションを追加すると、そのレイヤーがアニメーションを所有するため、アニメーションの保持カウントは当然増加します。
GorillaPatch 2010

16
機能しません-停止セレクターが呼び出されるまでに、アニメーションは存在しません。nullの参照を取得します。
アダム

4
これはforKey:パラメータの誤用であり、その必要はありません。Batgarが行っていたことは正確です。Key-Valueコーディングにより、任意のデータをアニメーションに添付できるため、簡単に識別できます。
マット2012年

7
Adamさん、以下のjimtの回答を参照してください– が呼び出されanim.removedOnCompletion = NO;てもまだ存在するように設定する必要があります-animationDidStop:finished:
ヤンマイヤー

46

CAAnimationsの完了コードを実行するさらに優れた方法を思いついたばかりです。

ブロックのtypedefを作成しました:

typedef void (^animationCompletionBlock)(void);

アニメーションにブロックを追加するために使用するキー:

#define kAnimationCompletionBlock @"animationCompletionBlock"

次に、CAAnimationの終了後にアニメーション完了コードを実行する場合は、アニメーションのデリゲートとして自分自身を設定し、setValue:forKeyを使用してコードブロックをアニメーションに追加します。

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

次に、animationDidStop:finished:メソッドを実装します。これは、指定されたキーでブロックをチェックし、見つかった場合はそれを実行します。

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

このアプローチの優れた点は、アニメーションオブジェクトを作成するのと同じ場所にクリーンアップコードを記述できることです。さらに良いことに、コードはブロックなので、コードが定義されているスコープのローカル変数にアクセスできます。userInfo辞書などのナンセンスな設定をいじる必要はありません。また、さまざまな種類のアニメーションを追加するにつれて、ますます複雑になるアニメーションDidStop:finished:メソッドを記述する必要はありません。

正直なところ、CAAnimationには完了ブロックプロパティが組み込まれている必要があり、指定されている場合は、それを自動的に呼び出すシステムサポートが必要です。ただし、上記のコードは同じ機能を数行の追加コードで提供します。


7
誰かがこのためにCAAnimationのカテゴリをまとめました:github.com/xissburg/CAAnimationBlocks
Jay Peyer

これは正しくないようです。theBlock();呼び出された直後にEXEC_Errを受け取ることがよくあります。これは、ブロックのスコープが破棄されたことが原因であると考えています。
mahboudz

私はしばらくの間このブロックを使用しており、それはAppleの恐ろしい「公式」のアプローチよりもはるかにうまく機能します。
アダム

3
プロパティの値として設定する前に、そのブロックを[ブロックコピー]する必要があると確信しています。
フィオナホプキンス

1
いいえ、ブロックをコピーする必要はありません。
Duncan C

33

2番目の方法は、アニメーションを実行する前に、完了時にアニメーションが削除されないように明示的に設定した場合にのみ機能します。

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

そうしないと、アニメーションは完了する前に削除され、コールバックは辞書でそれを見つけられません。


10
これはコメントではなく、回答である必要があります。
ティル

2
後でremoveAnimationForKeyを使用して明示的に削除する必要があるのでしょうか?
bompf 2013年

それは本当にあなたが何をしたいかによります。必要に応じてそれを削除することも、他に何かを並行して実行したい場合はそのままにすることもできます。
applejack42

31

他のすべての答えはあまりにも複雑です!アニメーションを識別するための独自のキーを追加してみませんか?

このソリューションは非常に簡単で、アニメーションに独自のキーを追加するだけです(この例では、animationID)。

この行を挿入して、animation1を識別します。

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

そして、これはanimation2を識別するために:

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

次のようにテストします。

- (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
    }
}

インスタンス変数は必要ありません


私はanimationDidStopにいくつかのint型の値(INT(0))を取得しています[animation valueForKey:@"animationID"]
abhimuralidharan

14

上記の内容(および数時間の無駄な時間の後にここに私をもたらしたもの)を明確にするために:割り当てられた元のアニメーションオブジェクトが返されたとは思わないでください。

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

アニメーションが終了すると、アニメーション[CALayer addAnimation:forKey:]のコピーが作成されます。

信頼できるのは、アニメーションオブジェクトに指定したキー設定された値が、animationDidStop:finished:メッセージと共に渡されたレプリカアニメーションオブジェクトに同等の値(ただし、必ずしもポインタと同等ではない)で存在するということです。上記のように、KVCを使用すると、状態を保存および取得するための十分なスコープが得られます。


1
+1これが最善の解決策です!でアニメーションの「名前」を[animation setValue:@"myanim" forKey:@"name"]設定でき、を使用してアニメーション化されているレイヤーを設定することもできます[animation setValue:layer forKey:@"layer"]。これらの値は、デリゲートメソッド内で取得できます。
trojanfoe 2012

valueForKey:nil私のために戻ります、理由は何ですか?
Iulian Onofrei 2016年

@IulianOnofreiは、アニメーションが同じプロパティの別のアニメーションによって置き換えられていないことを確認します—予期しない副作用として発生する可能性があります。
t0rst

@ t0rst申し訳ありませんが、複数のアニメーションがあり、コピーと貼り付けを使用して、同じアニメーション変数に異なる値を設定していました。
Iulian Onofrei 2016年

2

上記の最良の回答に基づいて、迅速な2.3のために作成するobjcの回答をほとんど見ることができます。

はじめに、これらのすべてのキーをプライベート構造体に格納しておくとよいので、タイプセーフであり、将来コードを変更しても、コードのすべての場所でキーを変更するのを忘れたというだけで、煩わしいバグが発生することはありません。

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

ご覧のとおり、変数/アニメーションの名前を変更したので、より明確になっています。次に、アニメーションの作成時にこれらのキーを設定します。

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

(...)

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

次に、アニメーションが停止したときのデリゲートを最後に処理します

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
        }
    }
}

0

AppleのKey-Valueを使用する私見はこれを行うエレガントな方法です。これは特に、アプリケーション固有のデータをオブジェクトに追加できるようにすることを目的としています。

他のはるかにエレガントでない可能性は、アニメーションオブジェクトへの参照を保存し、それらを識別するためにポインター比較を行うことです。


これは機能しません。Appleがポインターを変更するため、ポインターの等価を行うことはできません。
アダム

0

2つのCABasicAnimationオブジェクトが同じアニメーションであるかどうかを確認するために、keyPath関数を使用してそれを正確に行います。

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

  • CABasicAnimationはアニメーション化しないため、KeyPathを設定する必要はありません。

質問はデリゲートコールバックに関連し、keyPathはCAAnimationのメソッドではありません
Max MacLeod

0

使用setValue:forKeyしたいのは:アニメーションしているビューの参照を保持するために、同じ種類のアニメーションを異なるレイヤーに追加できるため、IDに基づいてアニメーションを一意に識別しようとするよりも安全です。

これら2つは同等です。

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

これで:

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;

そしてデリゲートメソッドで:

- (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

Xcode 9 Swift 4.0

キー値を使用して、追加したアニメーションを、animationDidStopデリゲートメソッドで返されるアニメーションに関連付けることができます。

すべてのアクティブなアニメーションと関連する完了を含むように辞書を宣言します。

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

アニメーションを追加するときに、そのキーを設定します。

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

animationDidStopでは、魔法が起こります。

    let animObject = anim as NSObject
    if let keyValue = animObject.value(forKey: "CompletionId") as? Int {
        if let completion = animating.removeValue(forKey: keyValue) {
            completion()
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.