如何在 animationDidStop 委托中识别 CAAnimation?

Posted

技术标签:

【中文标题】如何在 animationDidStop 委托中识别 CAAnimation?【英文标题】:How to identify CAAnimation within the animationDidStop delegate? 【发布时间】:2009-08-10 14:04:08 【问题描述】:

我遇到了一个问题,即我有一系列重叠的 CATransition / CAAnimation 序列,当动画停止时我需要执行自定义操作,但我只想要一个用于 animationDidStop 的委托处理程序。

但是,我遇到了一个问题,似乎没有一种方法可以唯一地标识 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,当我搜索“iphone animationDidStop identify”时,第一个点击是您的帖子,建议使用键值来识别动画。正是我需要的,谢谢。鲁迪 请注意 CAAnimationdelegate 很强大,因此您可能需要将其设置为 nil 以避免保留循环! 【参考方案1】:

巴特加的技术太复杂了。为什么不利用 addAnimation 中的 forKey 参数呢?它就是为此目的而设计的。只需取出对 setValue 的调用并将键字符串移动到 addAnimation 调用。例如:

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

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

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

【讨论】:

我想提一下,使用上述增量保留计数!被警告。也就是说,animationForKey: 增加 CAAnimation 对象的保留计数。 @mmilo 这并不令人惊讶,是吗?通过向图层添加动画,图层拥有动画,因此动画的保留计数当然会增加。 不起作用 - 在调用停止选择器时,动画不再存在。你得到一个空引用。 这是对forKey:参数的误用,没有必要。 Batgar 所做的完全正确 - 键值编码允许您将任意数据附加到动画中,因此您可以轻松识别它。 Adam,请参阅下面的 jimt 答案 - 您必须设置 anim.removedOnCompletion = NO; 以便在调用 -animationDidStop:finished: 时它仍然存在。【参考方案2】:

我刚刚想出了一个更好的方法来为 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 应该有一个内置的完成块属性,并且系统支持在指定时自动调用它。但是,上面的代码只需几行额外代码即可为您提供相同的功能。

【讨论】:

有人还为此在 CAAnimation 上建立了一个类别:github.com/xissburg/CAAnimationBlocks 这似乎不对。很多时候,我在theBlock(); 被调用后立即得到一个 EXEC_Err,我相信这是由于块的范围被破坏了。 我已经使用了一段时间了,它比 Apple 糟糕的“官方”方法好得多。 我很确定在将其设置为属性值之前,您需要 [阻止复制] 该块。 不,你不需要复制块。【参考方案3】:

第二种方法只有在您明确地将动画设置为在运行之前完成时才有效:

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

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

【讨论】:

这应该是评论,而不是答案。 我想知道之后是否有必要使用 removeAnimationForKey 明确删除它? 这真的取决于你想做什么。如有必要,您可以将其删除,也可以将其保留,因为您想同时做其他事情。【参考方案4】:

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

此解决方案非常简单,您只需将自己的键添加到动画中(本例中为动画ID)

插入此行以识别 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"]【参考方案5】:

为了明确上面的暗示(以及在浪费了几个小时后将我带到这里的原因):不要期望看到您分配的原始动画对象通过

传递给您
 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag 

动画结束时,因为[CALayer addAnimation:forKey:] 会复制您的动画。

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

【讨论】:

+1 这是最好的解决方案!您可以使用[animation setValue:@"myanim" forKey:@"name"] 设置动画的“名称”,甚至可以使用[animation setValue:layer forKey:@"layer"] 设置动画层。然后可以在委托方法中检索这些值。 valueForKey: 为我返回 nil,知道为什么吗? @IulianOnofrei 检查您的动画是否被同一属性的另一个动画取代 - 可能会发生意外的副作用。 @t0rst,抱歉,有多个动画并使用复制粘贴,我在同一个动画变量上设置了不同的值。【参考方案6】:

我可以看到大部分 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
        
    

【讨论】:

【参考方案7】:

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

【讨论】:

【参考方案8】:

恕我直言,使用 Apple 的 key-value 是一种优雅的方式:它专门用于允许向对象添加特定于应用程序的数据。

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

【讨论】:

这永远行不通——你不能做指针等价,因为苹果改变了指针。【参考方案9】:

让我检查 2 个 CABasicAnimation 对象是否是相同的动画, 我使用 keyPath 函数来做到这一点。

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

没有必要为 CABasicAnimation 设置 KeyPath,因为它不再动画了

【讨论】:

问题与委托回调有关,keyPath 不是 CAAnimation 上的方法【参考方案10】:

我喜欢使用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];
    

【讨论】:

以上是关于如何在 animationDidStop 委托中识别 CAAnimation?的主要内容,如果未能解决你的问题,请参考以下文章

CABasicAnimation - 未调用 animationDidStop

AnimationDidStop 没有被调用

animationDidStop:finished: 未调用

CAAnimationGroup 中的 CAAnimation 委托

立即调用 animationDidStop 方法

在 CABasicAnimation 中闪烁以进行旋转