如何在animationDidStop委托中识别CAAnimation?

时间:2009-08-10 14:04:09

标签: iphone core-animation

我遇到一个问题,我有一系列重叠的CATransition / CAAnimation序列,所有这些都是我需要在动画停止时执行自定义操作,但我只想要一个动画片段的委托处理程序。

但是,我遇到了一个问题,似乎没有办法在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.
 }

这方面的另一个方面是它允许您将状态保持在键值配对系统中,而不必将其存储在您的委托类中。代码越少越好。

请务必查看Apple Reference on Key Value Pair Coding

在animationDidStop委托中是否有更好的CAAnimation / CATransition识别技术?

谢谢, --Batgar

10 个答案:

答案 0 :(得分:88)

Batgar的技术太复杂了。为什么不利用addAnimation中的forKey参数?它的目的是为了这个目的。只需取出对setValue的调用并将键字符串移动到addAnimation调用即可。例如:

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

然后,在animationDidStop回调中,您可以执行以下操作:

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

答案 1 :(得分: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词典或其他类似的废话,也不必编写一个不断增长的animationDidStop:finished:方法,当您添加不同类型的动画时,它会变得越来越复杂。

说实话,CAAnimation应该内置一个完成块属性,如果指定了一个属性,系统支持自动调用它。但是,上面的代码只为几行额外的代码提供了相同的功能。

答案 2 :(得分:33)

第二种方法只有在运行之前明确将动画设置为 not 时才会被删除:

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

如果您没有这样做,您的动画将在完成之前被删除,并且回调将无法在字典中找到它。

答案 3 :(得分:30)

所有其他答案都太复杂了!为什么不直接添加自己的密钥来识别动画呢?

这个解决方案非常简单,只需将自己的密钥添加到动画(本例中为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
    }
}

它不需要任何实例变量

答案 4 :(得分:12)

明确从上面隐含的内容(以及在浪费时间之后将我带到这里的原因):不要期望看到你分配的原始动画对象通过

返回给你
 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag 

动画结束时,因为[CALayer addAnimation:forKey:]制作了动画的副本。

您可以依赖的是,您为动画对象提供的键控值仍然在使用animationDidStop:finished:消息传递的副本动画对象中具有等效值(但不一定是指针等效)。如上所述,使用KVC可以获得足够的存储和检索状态。

答案 5 :(得分:1)

根据上面的最佳答案,我可以看到主要是objc的答案我将为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
        }
    }
}

答案 6 :(得分:0)

使用Apple的键值的恕我直言是这样做的优雅方式:它专门用于允许向对象添加特定于应用程序的数据。

其他不太优雅的可能性是存储对动画对象的引用,并进行指针比较以识别它们。

答案 7 :(得分:0)

我来检查2个CABasicAnimation对象是否是同一个动画, 我使用keyPath函数完全一样。

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

  • 无需为CABasicAnimation设置KeyPath,因为它将不再设置动画

答案 8 :(得分:0)

我喜欢使用setValue:forKey:保留我动画视图的参考,比尝试根据ID唯一识别动画更安全,因为相同类型的动画可以添加到不同的图层。

这两个是等价的:

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

答案 9 :(得分: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()
        }
    }