使用标签栏关闭视图控制器后动画不会停止

Posted

技术标签:

【中文标题】使用标签栏关闭视图控制器后动画不会停止【英文标题】:Animations not stopping after view controller is dismissed using tab bar 【发布时间】:2016-09-14 19:39:51 【问题描述】:

问题 我有两个视图控制器,它们都包含在各自的 UINavigationControllers 和一个 UITabBarController 中。在其中一个视图控制器上,我正在创建气泡效果,在屏幕上绘制气泡并为其位置设置动画。当我使用标签栏移动到另一个视图控制器时会出现问题,这会导致 CPU 达到峰值并保持在 100% 并且气泡继续动画。

代码 气泡的代码封装在 UIView 子类中。

override func draw(_ rect: CGRect) 
    // spawn shapes
    for _ in 1 ... 10  // spawn 75 shapes initially
      spawn()
    
  

drawRect 方法反复调用spawn 函数以用气泡填充视图。

fileprivate func spawn() 
    let shape = CAShapeLayer()
    shape.opacity = 0.0

    // create an inital path at the starting position
    shape.path = UIBezierPath(arcCenter: CGPoint.zero, radius: 1, startAngle: 0, endAngle: 360 * (CGFloat.pi / 180), clockwise: true).cgPath
    shape.position = CGPoint.zero

    layer.addSublayer(shape)


    // add animation group
    CATransaction.begin()

    let radiusAnimation = CABasicAnimation(keyPath: "path")
    radiusAnimation.fromValue = shape.path
    radiusAnimation.toValue = UIBezierPath(arcCenter: center, radius: 100, startAngle: 0, endAngle: 360 * (CGFloat.pi / 180), clockwise: true).cgPath

    CATransaction.setCompletionBlock  [unowned self] in

      // remove the shape
      shape.removeFromSuperlayer()
      shape.removeAllAnimations()

      // spawn a new shape
      self.spawn()
    

    let movementAnimation = CABasicAnimation(keyPath: "position")
    movementAnimation.fromValue = NSValue(cgPoint: CGPoint.zero)
    movementAnimation.toValue = NSValue(cgPoint: CGPoint(x: 100, y: 100))


    let animationGroup = CAAnimationGroup()
    animationGroup.animations = [radiusAnimation, movementAnimation]
    animationGroup.fillMode = kCAFillModeForwards
    animationGroup.isRemovedOnCompletion = false
    animationGroup.duration = 2.0

    shape.add(animationGroup, forKey: "bubble_spawn")

    CATransaction.commit()
  

CATransaction 完成处理程序中,我从超级视图中删除形状并创建一个新形状。 self.spawn() 的函数调用似乎是问题所在

在包含视图控制器的 viewDidDisappear 上,我调用以下内容:

func removeAllAnimationsFromLayer() 

    layer.sublayers?.forEach( (layer) in
      layer.removeAllAnimations()
      layer.removeFromSuperlayer()
    )

    CATransaction.setCompletionBlock(nil)
  

来自答案的尝试 我尝试将removeAllAnimations 函数添加到UITabBarControllerDelegate

extension BaseViewController: UITabBarControllerDelegate 

  func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) 

    bubblesView.removeAllAnimationsFromLayer()
  

【问题讨论】:

completionBlock 中删除self.spawn() 会发生什么? 一旦动画完成,形状就会停止重新生成。如果删除此行的动画正在进行中并且下一个视图控制器被推送,则不会发生此问题 您可以添加一个布尔标志检查来控制是否为spawn。只是一个想法:D 谢谢!这实际上是我目前的(希望是)临时解决方案。虽然我希望找到问题的根本原因。 欢迎您! .. 在这里,您在 completion 上设置动画和结束动画,阻止您再次调用 animate start,这意味着您处于带有无限循环的 recursion 上,这就是它一直在动画的原因,您只需要控制并停止它动画! 【参考方案1】:

我认为你的问题是,你只使用一个线程来处理所有这些东西。请尝试将影响您的 GUI 的所有内容分配给主线程,并可能明确地将新的 spawn 实例分配给其他线程。看看情况如何。像这样的:

fileprivate func spawn() 

    let shape = CAShapeLayer()
    shape.opacity = 0.0

    // create an inital path at the starting position
    shape.path = UIBezierPath(arcCenter: CGPoint.zero, radius: 1, startAngle: 0, endAngle: 360 * (CGFloat.pi / 180), clockwise: true).cgPath
    shape.position = CGPoint.zero


    // create an inital path at the starting position
    shape.path = UIBezierPath(arcCenter: startingPosition, radius: startRadius, startAngle: BubbleConstants.StartingAngle, endAngle: BubbleConstants.EndAngle, clockwise: true).cgPath
    shape.position = startingPosition

    // set the fill color
    shape.fillColor = UIColor.white.cgColor

    layer.addSublayer(shape)

    shape.opacity = Float(opacity)

    DispatchQueue.main.async 
        self.layer.addSublayer(shape)
        CATransaction.begin()
    

    let radiusAnimation = CABasicAnimation(keyPath: "path")
    radiusAnimation.fromValue = shape.path
    radiusAnimation.toValue = UIBezierPath(arcCenter: center, radius: endRadius, startAngle: BubbleConstants.StartingAngle, endAngle: BubbleConstants.EndAngle, clockwise: true).cgPath


    DispatchQueue.main.async  [unowned self] in
        CATransaction.setCompletionBlock  [unowned self] in

            // remove the shape
            DispatchQueue.main.async 
                shape.removeFromSuperlayer()
                shape.removeAllAnimations()
            

            DispatchQueue.global(qos: .background).async 
                // spawn a new shape
                self.spawn()
            
        
    


    let movementAnimation = CABasicAnimation(keyPath: "position")
    movementAnimation.fromValue = NSValue(cgPoint: startingPosition)
    movementAnimation.toValue = NSValue(cgPoint: destination)


    let animationGroup = CustomAnimationGroup()
    animationGroup.animations = [radiusAnimation, movementAnimation]
    animationGroup.fillMode = kCAFillModeForwards
    animationGroup.isRemovedOnCompletion = false
    animationGroup.duration = duration

    shape.add(animationGroup, forKey: "bubble_spawn")

    DispatchQueue.main.async 
        CATransaction.commit()
    

【讨论】:

感谢您的详细回答,我不得不承认我没有使用您之前建议的大部分内容。我已尽力实施您的建议,同时也翻译成 Swift 3.0,不幸的是问题仍然存在。虽然如果您不介意快速检查以确保我已正确转换线程代码,我将非常感激。 gist.github.com/ollie-eman/3eccd3e015a9774a31696772ef9df817 我为 Swift 3 调整了我的示例。现在调度更加舒适。 :) @Ollie 对于你在 github 上提供的代码,我认为 DispatchQueue.main.sync 对你没有帮助,至少尝试使用 DispatchQueue.main.async (async vs sync) 忘了说,是的,请像我的例子一样使用异步。后台线程调度现在就像DispatchQueue.global(qos: .background).async 一样简单。 美女!在用DispatchQueue.global(qos: .background).async 包围self.spawn() 后,在选项卡之间交换时CPU 峰值已得到修复。【参考方案2】:

在 UITabBarController 中,关联的视图控制器具有扁平结构。即每个选项卡中的视图控制器独立运行。

因此,必须在委托方法中添加func removeAllAnimationsFromLayer()

func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController)

【讨论】:

感谢您的回复,很遗憾问题仍然存在。我已将您的答案的实现添加到问题的底部。如果我执行不正确,请大喊

以上是关于使用标签栏关闭视图控制器后动画不会停止的主要内容,如果未能解决你的问题,请参考以下文章

iOS - 关闭视图控制器后标签栏变为透明

显示和关闭视图控制器后,iOS 7.1 上的标签栏背景丢失

关闭模态呈现的视图控制器后切换标签栏控制器视图控制器

关闭标签栏控制器

出现后无法关闭视图控制器

iOS 8.4 状态栏动画故障