首页 文章

如何在animationDidStop委托中识别CAAnimation?

提问于
浏览
95

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

但是,我遇到了一个问题,似乎没有办法在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识别技术?

谢谢, - 巴特

10 回答

  • 0

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

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

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

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

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

  • 46

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

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

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

  • 1

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

    这个解决方案非常简单,只需 add your own key to the animation (本例中为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
        }
    }
    

    It does not require any instance variables

  • 0

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

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

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

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

  • 33

    基于上面的最佳答案,我可以看到主要是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
            }
        }
    }
    
  • 12

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

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

  • 30

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

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

    • 无需为CABasicAnimation设置KeyPath,因为它将不再设置动画
  • 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()
            }
        }
    

相关问题