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

标签 swift animation rotation continuous

我想使用一个按钮作为切换按钮——单击一次,图像就会无限期地旋转。再次点击,图像停止,再次点击,它重新开始。

我发现这个答案有助于让动画继续: Rotate a view for 360 degrees indefinitely in Swift?

但是,我不清楚如何停止。我已经实现了下面的代码并且它似乎可以工作,但我很好奇这是否是停止动画的正确方法,或者是否有另一种首选方法。另外 - 我的旋转一直持续到完成,但我想知道我是否可以在按下按钮时将旋转卡住在某个位置(我在下面的第二次尝试中尝试了 .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)
        }
    }

这确实有效。我还想知道是否有可能在旋转过程中停止动画。按照它的设置方式,动画在停止前旋转了 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)
        }
    }

欢迎确认第一种方法是否合理。如果有一种方法可以在旋转中途停止旋转,那也很受欢迎(同时让我直截了本地了解我对 removeAllAnimations 的错误想法)。

谢谢! JG

最佳答案

有几种方法可以满足您的要求:

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

     private var animator: UIViewPropertyAnimator?
    
     deinit {
         animator?.stopAnimation(true)
     }
    
     @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) { [self] in
             UIView.animateKeyframes(withDuration: 4, delay: 0) {
                 UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
                     animatedView.transform = .init(rotationAngle: .pi * 2 * 1 / 3)
                 }
                 UIView.addKeyframe(withRelativeStartTime: 1.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                     animatedView.transform = .init(rotationAngle: .pi * 2 * 2 / 3)
                 }
                 UIView.addKeyframe(withRelativeStartTime: 2.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                     animatedView.transform = .identity
                 }
             }
         } completion: { [weak self] _ in
             self?.createAnimation()
         }
     }
    
  2. 您也可以使用 UIKit Dynamics 来旋转项目。然后,您可以删除执行旋转的 UIDynamicItemBehavior,它就停在原处。它会自动将 View transform 留在原处。然后,要恢复旋转,只需再次为旋转添加一个 UIDynamicItemBehavior:

     private lazy var animator = 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)
         }
     }
    

    这不会让您轻松地控制时间方面的旋转速度,而是它由 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
     }
    
  4. 如果您想使用基于 UIView block 的动画,您必须捕获停止动画的角度,以便知道从哪里重新开始动画。诀窍是获取 CATransform3Dm12m11:

     angle = atan2(transform.m12, transform.m11)
    

    因此,这会产生:

     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) { [self] in
                 UIView.animateKeyframes(withDuration: 4, delay: 0, options: .repeat) {
                     UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
                         animatedView.transform = .init(rotationAngle: self.angle + .pi * 2 * 1 / 3)
                     }
                     UIView.addKeyframe(withRelativeStartTime: 1.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                         animatedView.transform = .init(rotationAngle: self.angle + .pi * 2 * 2 / 3)
                     }
                     UIView.addKeyframe(withRelativeStartTime: 2.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                         animatedView.transform = .init(rotationAngle: self.angle)
                     }
                 }
             }
         }
    
         isAnimating = !isAnimating
     }
    
  5. 您可以使用 CADisplayLink 自行旋转对象,将角度更新为某个计算值。然后停止旋转就像使显示链接无效一样简单,从而将其留在停止时的位置。然后,您可以通过简单地将显示链接添加回您的运行循环来恢复动画。

    这种技术为您提供了很大的控制权,但却是最不优雅的方法。

关于swift - 停止无限旋转图像的正确方法?以及如何实现 removeAllAnimations?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46208916/

相关文章:

ios - 如何在 `Quick Help` 的 Swift 注释中添加新行

ios - UIActivityViewController 发送带附件的电子邮件

html - 为什么这个 CSS @keyframes 规则没有淡入淡出?

android - 如何在 Android 中的 View 上应用缓动动画功能

具有旋转和平移问题的 CSS 透视图

ios - delaySubscription 不适用于 rx_tap

ios - 在 Swift 中使用字符串将类分配给变量

javascript - jQuery Accordion 菜单动画摆动

iphone - 旋转 iOS 6 兼容 iOS 5

javascript寻找旋转矩形的中心