【问题标题】:How to remove a layer when its animation completes?动画完成后如何删除图层?
【发布时间】:2013-07-15 08:04:12
【问题描述】:

我正在制作一个 iOS 应用。我有几个CALayer 对象最终将被(缩小的)动画删除。当动画完成并调用animationDidStop:finished 时,我想从超级视图 中移除 CALayer 对象并将其删除。

  1. 但是我怎样才能得到CALayer 对象 animationDidStop:finished?我会猜到 CAanimation-object 有一个指向该层的指针,但我找不到它 在文档中。
  2. 有没有更好的方法来处理这个问题? (实际上,我在同一个图层中添加了多个动画对象,理想情况下,我希望仅在最后一个动画完成时删除该图层)

【问题讨论】:

    标签: iphone ios animation calayer caanimation


    【解决方案1】:

    看看这个答案是否有帮助:Perform an action after the animation has finished 我发现 animateWithDuration:animations:completion: 比直接使用 CALayer 更容易使用。您可以通过完成处理程序链接多个动画,然后删除最后一个中的图层。例如:

    [UIView animateWithDuration:1.0 animations:^{
        // do first animation in the sequence
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:1.0 animations:^{
            // do second animation in the sequence
        } completion:^(BOOL finished) {
            [UIView animateWithDuration:1.0 animations:^{
                // do third animation in the sequence
            } completion:^(BOOL finished) {
                // remove layer after all are done
            }];
        }];
    }];
    

    这种方式可能有点混乱,但您可以将它们重构为它们自己的方法调用,例如。

    【讨论】:

    • 我不确定如何使用 blocks 为 CALayer 对象设置动画(在 ioS5 或 6 中)。
    【解决方案2】:

    当您创建动画并设置代理时,只需将您要与动画一起移除的 CALayer 传递。

    至于删除所有动画,您有两种选择:

    1. 您可以检查您的 CALayer 的动画键是否存在任何现有动画。
    2. 您可以使用 CAAnimationGroup 并将所有动画组合在一起。

    【讨论】:

    • 我正在调用 addAnimation:forKey:,如何传递 CALayer 对象?
    • 你可以在你的委托上有一个 NSMutableArray 属性。任何时候使用 addAnimation:forKey: 将该 CALayer 添加到 NSMutableArray。然后你可以从你的委托中引用所有的 CALayers。
    • 所以你的意思是 animationDidStop:finished: 应该扫描该可变数组以查看它是否可以找到任何没有正在进行的动画的图层对象,然后将它们从两者中删除数组和超层?
    • 是的,或者如果你所有的动画都在一个 CALayer 上,你可以跳过 NSMutableArray。 CAAnimations 是相当低级的。除了动画之外,他们不带任何东西。这就是 UIView 块动画更常见的原因。
    • 我已经发布了一个替代解决方案,但我检查了您的解决方案作为答案!
    【解决方案3】:

    另一种解决方案是向动画对象的字典添加层指针,如下所示

    // in some define section
    #define kAnimationRemoveLayer @"animationRemoveLayer"
    

    那么,在animationDidStop中,

    - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag{
        CALayer *lay = [theAnimation valueForKey:kAnimationRemoveLayer];
        if(lay){
            [lay removeAllAnimations];
            [lay removeFromSuperlayer];
        }
    }
    

    最后,在动画设置中,

    CALAyer * lay = ... ;
    BOOL    shouldRemove = .... ; 
    CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:@"position"];
    anim.delegate = self;
    if (shouldRemove)
        [anim setValue:lay forKey:kAnimationRemoveLayer];
    

    【讨论】:

      【解决方案4】:

      这个问题已经很久没有回答了,但是我会尝试添加一个更 swifty 的解决方案,以防现在有人仍在寻找更干净的解决方案。

      如果您感兴趣的只是在 CAAnimation 完成后立即删除该层,您可以将动画的委托分配给一个简单的 NSObject,该 NSObject 持有对目标层的引用并等待动画回调以便关闭它。

      我们称这个辅助对象为LayerRemover:

      class LayerRemover: NSObject, CAAnimationDelegate {
          private weak var layer: CALayer?
      
          init(for layer: CALayer) {
              self.layer = layer
              super.init()
          }
      
          func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
              layer?.removeFromSuperlayer()
          }
      }
      

      此对象所做的一切都是通过初始化程序接收 CALayer 引用,并在移除层之前等待 animationDidStop 回调。此时,一旦通过委托属性保留它的 CAAnimation 被取消初始化,Layer remover 也会被清除。

      现在,您所要做的就是将这个卸妆器安装并使用它:

      let layer = CAShapeLayer()
      layer.path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 200, height: 100)).cgPath
      let myAnimation = CABasicAnimation(keyPath: "strokeEnd")
      ...
      myAnimation.delegate = LayerRemover(for: layer)
      

      就是这样!

      请注意,您不必保留对 LayerRemover 对象的任何引用,因为我们可以从 Apple documentation 中读取到

      委托对象由接收者保留。这是高级内存管理编程指南中描述的内存管理规则的罕见例外。

      【讨论】:

      • 喜欢这个解决方案!也感谢您对内存管理细节的额外关注。
      • 这是一个非常干净的解决方案,比跟踪animationDidStop(:finished:) 中的每个动画要好得多。谢谢! ?
      猜你喜欢
      • 1970-01-01
      • 2020-05-11
      • 1970-01-01
      • 2010-11-28
      • 1970-01-01
      • 2016-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多