如何在animationDidStop委托中标识CAAnimation?


102

我遇到了一个问题,即我有一系列重叠的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参考

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

谢谢,-蝙蝠侠


4
batgar,当我在Google上搜索“ iphone animationDidStopident”时,第一个匹配项是您的帖子,建议您使用键值来识别动画。正是我需要的,谢谢。鲁迪
rudifa

1
请注意,CAAnimations delegate很强,因此您可能需要对其进行设置nil以避免保留周期!
Iulian Onofrei,2016年

Answers:


92

Batgar的技术太复杂了。为什么不利用addAnimation中的forKey参数呢?正是出于这个目的。只需取消对setValue的调用,然后将键字符串移至addAnimation调用即可。例如:

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

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

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

我想提一下,使用以上增加的余数!被警告。也就是说,animationForKey:增加CAAnimation对象的保留计数。
mmilo 2010年

1
@mmilo这不是很令人惊讶,是吗?通过将动画添加到图层,该图层将拥有该动画,因此动画的保留计数当然会增加。
GorillaPatch

16
不起作用-调用停止选择器时,动画不再存在。您会得到一个空引用。
亚当

4
这是对forKey:参数的滥用,并且不需要它。Batgar所做的工作是完全正确的-键值编码允许您将任意数据附加到动画中,因此您可以轻松识别它。
马特2012年

7
亚当,请参见下面jimt的答案-您必须进行设置anim.removedOnCompletion = NO;,以使其在-animationDidStop:finished:被调用时仍然存在。
Yang Meyer

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应该内置一个完成块属性,如果指定了CAAnimation,则系统支持自动调用它。但是,以上代码为您提供了相同的功能,仅需几行额外的代码。


7
有人为此在CAAnimation上放了一个类别:github.com/xissburg/CAAnimationBlocks
Jay Peyer

这似乎不对。通常,在theBlock();调用之后我会立即得到EXEC_Err ,并且我相信这是由于该块的作用域被破坏的事实所致。
mahboudz 2012年

我已经使用了一段时间了,它的工作方式比苹果公司糟糕的“官方”方法要好得多。
亚当

3
我相当确定在将其设置为属性值之前,您需要[该块的复制]。
菲奥娜·霍普金斯

1
不,您不需要复制块。
Duncan C

33

第二种方法仅在显式设置动画在完成之前将其删除时才有效:

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

如果您不这样做,动画将在完成之前被删除,并且回调函数将在字典中找不到它。


10
这应该是评论,而不是答案。
直到2012年

2
我想知道是否需要使用removeAnimationForKey之后将其显式删除?
bompf

这真的取决于您要做什么。如果需要,您可以删除它,也可以保留它,因为您想同时进行其他操作。
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 '16

@IulianOnofrei检查您的动画没有被具有相同属性的另一个动画取代-可能会发生意外的副作用。
t0rst

@ t0rst,抱歉,有多个动画并使用复制粘贴,我在同一动画变量上设置了不同的值。
Iulian Onofrei

2

我可以看到大多数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
        }
    }
}

0

恕我直言,使用Apple的键值是执行此操作的一种优雅方式:它专门用于允许向对象添加特定于应用程序的数据。

另一种不太优雅的方法是存储对动画对象的引用,并进行指针比较以识别它们。


这将永远行不通-您无法进行指针等效,因为Apple会更改指针。
亚当

0

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

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

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

这个问题与委托回调有关,并且keyPath不是CAAnimation上的方法
Max

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

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()
        }
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.