停止无限旋转图像的正确方法?以及如何实现removeAllAnimations? [英] Proper way to stop an infinitely rotating image? and how does one implement removeAllAnimations?

查看:128
本文介绍了停止无限旋转图像的正确方法?以及如何实现removeAllAnimations?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'd like to use a button as a toggle – click once & an image rotates indefinitely. Click again, the image stops, click again, it restarts.

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

I found this answer helpful in getting the animation to continue: Rotate a view for 360 degrees indefinitely in Swift?

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

However, I'm unclear on how to stop things. I've implemented the code below & it seems to work, but am curious if this is the proper way to stop an animation, or if there is another, preferred method. Also - my rotation continues until finishing, but I'm wondering if I can freeze the rotation at location when the button is pressed (I've tried .removeAllAnimations() in the second attempt below, but that doesn't seem to work at all.

    @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就会更快(见下文):

This does work. I was also wondering if it'd be possible to stop the animation mid-spin. The way it's set up, the animation goes the full 180 degrees before stopping. I've also tried adding a removeAnimation in the spinPressed action, and getting rid of the stopRotation check inside rotateView, but that doesn't seem to work – rotation continues & just gets faster if the spinPressed is pressed again (see below):

    @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的错误思考)。

A confirm if first approach is sound is welcome. And if there is a way to stop the rotation mid-spin, that'd also be welcome (as well as setting me straight on my flawed thinking on removeAllAnimations).

谢谢!
JG

Thanks! JG

推荐答案

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

There are a couple of ways to do what you're asking about:


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

  1. If supporting iOS 10+, you can use UIViewPropertyAnimator, whose animations you can pause and restart (resuming from where it was paused):

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()
    })
}


  • 您也可以使用UIKit Dynamics旋转项目。然后,您可以删除正在执行旋转的 UIDynamicItemBehavior ,它只是停在原地。它会自动将视图 transform 保留在原位置。然后,要恢复旋转,只需再次添加 UIDynamicItemBehavior 进行旋转:

  • You can alternatively use UIKit Dynamics to rotate the item. You can then remove a UIDynamicItemBehavior that was performing the rotation and it just stops where it was. It automatically leaves the view transform where it was. Then, to resume the rotation, just add a UIDynamicItemBehavior for the rotation again:

    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!)
        }
    }
    

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

    This doesn't let you easily control the speed of the rotation in terms of time, but rather it’s dictated by angularVelocity, but it's a nice simple approach (and supports iOS 7.0 and later).

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

    The old-school approach for stopping an animation and leaving it where you stopped it is to capture the presentationLayer of the animation (which shows where it was mid-flight). Then you can grab the current state, stop the animation, and set the transform to what the presentationLayer reported.

    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
    }
    


  • 如果要使用基于块的 UIView 动画,则必须以您停止了动画,因此您知道从哪里重新开始动画。诀窍是抢夺 CATransform3D m12 m11

  • If you want to use UIView block based animation, you have to capture the angle at which you stopped the animation, so you know from where to restart the animation. The trick is grab m12 and m11 of the CATransform3D:

    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, 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
    }
    


  • 您可以使用 CADisplayLink 将角度更新为某些计算值。然后停止旋转就像使显示链接失效一样简单,从而将其保留在停止时的位置。然后,您只需将显示链接添加回您的运行循环即可恢复动画。

  • You can rotate the object yourself using CADisplayLink that updates the angle to some calculated value. Then stopping the rotation is as simple as invalidating the display link, thereby leaving it where it was when it stopped. You can then resume animation by simply adding the display link back to your runloop.

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

    This sort of technique gives you a great deal of control, but is the least elegant of the approaches.

    这篇关于停止无限旋转图像的正确方法?以及如何实现removeAllAnimations?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆