停止无限旋转图像的正确方法?以及如何实现removeAllAnimations?

Gal*_*her 3 animation rotation continuous swift

我想将按钮用作切换按钮-单击一次即可无限旋转图片。再次单击,图像停止,再次单击,重新启动。

我发现此答案有助于使动画继续进行: 在Swift中将视图无限地旋转360度?

但是,我不清楚如何停止事情。我已经实现了下面的代码,而且似乎可以正常工作,但是很好奇这是否是停止动画的正确方法,或者是否还有其他首选方法。另外-我的旋转持续进行到完成为止,但是我想知道是否可以在按下按钮时冻结该位置的旋转(我在下面的第二次尝试中尝试了.removeAllAnimations(),但似乎在所有。

    @IBOutlet weak var imageView: UIImageView!
    var stopRotation = true

    func rotateView(targetView: UIView, duration: Double = 1.0) {
        if !stopRotation {
            UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
                targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi))
            }) { finished in
                self.rotateView(targetView: targetView, duration: duration)
            }
        }
    }
    
    @IBAction func spinPressed(_ sender: UIButton) {
        stopRotation = !stopRotation
        if !stopRotation {
            rotateView(targetView: imageView)
        }
    }
Run Code Online (Sandbox Code Playgroud)

这确实有效。我还想知道是否有可能停止动画的自旋。设置方式是,动画在停止之前会完整旋转180度。我也尝试过在spinPressed动作中添加removeAnimation,并摆脱了rotateView内部的stopRotation检查,但这似乎不起作用–旋转会继续,并且如果再次按下spinPressed只会变得更快(请参见下文):

    @IBOutlet weak var imageView: UIImageView!
    var stopRotation = true
    
    func rotateView(targetView: UIView, duration: Double = 1.0) {
        UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
            targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi))
        }) { finished in
            self.rotateView(targetView: targetView, duration: duration)
        }
    }
    
    @IBAction func spinPressed(_ sender: UIButton) {
        stopRotation = !stopRotation
        if stopRotation {
            imageView.layer.removeAllAnimations()
        } else {
            rotateView(targetView: imageView)
        }
    }
Run Code Online (Sandbox Code Playgroud)

欢迎确认第一种方法是否正确。而且,如果有一种方法可以阻止旋转在中间旋转,那也将受到欢迎(以及使我直接意识到我对removeAllAnimations的错误思考)。

谢谢!JG

Rob*_*Rob 5

有两种方法可以解决您的问题:

  1. 如果支持iOS 10+,则可以使用UIViewPropertyAnimator,您可以暂停和重新启动其动画(从暂停的位置恢复):

    private var animator: UIViewPropertyAnimator?
    
    @IBAction func didTapButton(_ sender: Any) {
        guard let animator = animator else {
            createAnimation()
            return
        }
    
        if animator.isRunning {
            animator.pauseAnimation()
        } else {
            animator.startAnimation()
        }
    }
    
    /// Create and start 360 degree animation
    ///
    /// This will fire off another animation when one 360° rotation finishes.
    
    private func createAnimation() {
        animator = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 4, delay: 0, options: .curveLinear, animations: {
            UIView.animateKeyframes(withDuration: 4, delay: 0, animations: {
                UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
                    self.animatedView.transform = .init(rotationAngle: .pi * 2 * 1 / 3)
                }
                UIView.addKeyframe(withRelativeStartTime: 1.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                    self.animatedView.transform = .init(rotationAngle: .pi * 2 * 2 / 3)
                }
                UIView.addKeyframe(withRelativeStartTime: 2.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                    self.animatedView.transform = .identity
                }
            })
        }, completion: { [weak self] _ in
            self?.createAnimation()
        })
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 您也可以使用UIKit Dynamics旋转项目。然后UIDynamicItemBehavior,您可以删除正在执行旋转的,它只会停在原处。它会自动将视图transform保留在原位置。然后,要恢复旋转,只需UIDynamicItemBehavior再次为旋转添加一个:

    private lazy var animator: UIDynamicAnimator = UIDynamicAnimator(referenceView: view)
    private var rotate: UIDynamicItemBehavior?
    
    @IBAction func didTapButton(_ sender: Any) {
        if let rotate = rotate {
            animator.removeBehavior(rotate)
            self.rotate = nil
        } else {
            rotate = UIDynamicItemBehavior(items: [animatedView])
            rotate?.allowsRotation = true
            rotate?.angularResistance = 0
            rotate?.addAngularVelocity(1, for: animatedView)
            animator.addBehavior(rotate!)
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这不能让您轻松地控制时间的旋转速度,而是由决定的angularVelocity,但这是一个很好的简单方法(并支持iOS 7.0及更高版本)。

  3. 停止动画并将其留在停止位置的传统方法是捕获presentationLayer动画的(显示动画在飞行中的位置)。然后,您可以获取当前状态,停止动画,并将转换设置为所presentationLayer报告的内容。

    private var isAnimating = false
    
    @IBAction func didTapButton(_ sender: Any) {
        if isAnimating {
            let transform = animatedView.layer.presentation()!.transform
            animatedView.layer.removeAllAnimations()
            animatedView.layer.transform = transform
        } else {
            let rotate = CABasicAnimation(keyPath: "transform.rotation")
            rotate.byValue = 2 * CGFloat.pi
            rotate.duration = 4
            rotate.repeatCount = .greatestFiniteMagnitude
            animatedView.layer.add(rotate, forKey: nil)
        }
    
        isAnimating = !isAnimating
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 如果要使用UIView基于块的动画,则必须捕获停止动画的角度,这样才能知道从哪里重新开始动画。诀窍是抓住m12m11CATransform3D

    angle = atan2(transform.m12, transform.m11)
    
    Run Code Online (Sandbox Code Playgroud)

    因此,将产生:

    private var angle: CGFloat = 0
    private var isAnimating = false
    
    @IBAction func didTapButton(_ sender: Any) {
        if isAnimating {
            let transform = animatedView.layer.presentation()!.transform
            angle = atan2(transform.m12, transform.m11)
            animatedView.layer.removeAllAnimations()
            animatedView.layer.transform = transform
        } else {
            UIView.animate(withDuration: 4, delay: 0, options: .curveLinear, animations: {
                UIView.animateKeyframes(withDuration: 4, delay: 0, options: .repeat, animations: {
                    UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
                        self.animatedView.transform = .init(rotationAngle: self.angle + .pi * 2 * 1 / 3)
                    }
                    UIView.addKeyframe(withRelativeStartTime: 1.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                        self.animatedView.transform = .init(rotationAngle: self.angle + .pi * 2 * 2 / 3)
                    }
                    UIView.addKeyframe(withRelativeStartTime: 2.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                        self.animatedView.transform = .init(rotationAngle: self.angle)
                    }
                })
            })
        }
    
        isAnimating = !isAnimating
    }
    
    Run Code Online (Sandbox Code Playgroud)
  5. 您可以自己旋转对象CADisplayLink,该操作会将角度更新为一些计算值。然后停止旋转就像使显示链接失效一样简单,从而将其保留在停止时的位置。然后,您只需将显示链接添加回您的运行循环即可恢复动画。

    这种技术为您提供了很多控制权,但是却是最不完善的方法。