一連のオーバーラップする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つの側面は、デリゲートクラスに状態を保存する代わりに、キーと値のペアリングシステムで状態を保持できることです。コードが少ないほど良い。
Key Value Pair CodingのApple Reference を必ずチェックしてください。
AnimationDidStopデリゲートでCAAnimation/CATransitionを識別するためのより良いテクニックはありますか?
ありがとう、バトガー
Batgarのテクニックは複雑すぎます。 addAnimationのforKeyパラメーターを活用してみませんか?これはまさにこの目的のためのものでした。 setValueの呼び出しを取り出し、キー文字列をaddAnimation呼び出しに移動するだけです。例えば:
[[hearingAidHalo layer] addAnimation:animation forKey:@"Throb"];
次に、animationDidStopコールバックで、次のようなことができます。
if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...
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辞書やその他のナンセンスの設定を台無しにしたり、さまざまな種類のアニメーションを追加するにつれてますます複雑になるanimationDidStop:finished:メソッドを記述する必要はありません。
実を言うと、CAAnimationには補完ブロックプロパティが組み込まれている必要があり、指定されている場合は自動的に呼び出すためのシステムサポートが必要です。ただし、上記のコードは、わずか数行の追加コードで同じ機能を提供します。
2番目のアプローチは、アニメーションを実行する前に完了時に削除するnotに明示的に設定した場合にのみ機能します。
CAAnimation *anim = ...
anim.removedOnCompletion = NO;
そうしないと、アニメーションは完了する前に削除され、コールバックは辞書でそれを見つけられません。
他のすべての答えは非常に複雑です!アニメーションを識別するための独自のキーを追加してみませんか?
この解決策は非常に簡単ですアニメーションに独自のキーを追加(この例では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
}
}
インスタンス変数は必要ありません:
上から暗示されていることを明確にするため(そして数時間の無駄な時間を経て私をここに連れてきたのは):あなたが割り当てた元のアニメーションオブジェクトがあなたに渡されるのを期待しないでください
- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag
[CALayer addAnimation:forKey:]
がアニメーションのコピーを作成するため、アニメーションが終了したとき。
信頼できるのは、アニメーションオブジェクトに与えたキー付きの値が、animationDidStop:finished:
メッセージで渡されたレプリカアニメーションオブジェクト内に同等の値(ただし、ポインターの同等性とは限りません)のままであるということです。前述のように、KVCを使用すると、状態を保存および取得するための十分なスコープが得られます。
ほとんどの場合、上記の最良の回答に基づいてSwift 2.3.
最初はこれらのすべてのキーをプライベート構造体に保存しておくとよいので、タイプセーフであり、将来コードを変更するのを忘れたからといって迷惑なバグが発生することはありません。
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
}
}
}
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];
}
}
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()
}
}
AppleのKey-Valueを使用するIMHOは、これを行うエレガントな方法です。具体的には、アプリケーション固有のデータをオブジェクトに追加できるようにすることを目的としています。
他のはるかにエレガントな可能性は、アニメーションオブジェクトへの参照を保存し、それらを識別するためにポインター比較を行うことです。
2つのCABasicAnimationオブジェクトが同じアニメーションであるかどうかを確認するために、keyPath関数を使用してまったく同じことを行います。
if([animationA keyPath] == [animationB keyPath])