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

时间:2017-09-14 00:57:32

标签: swift animation rotation continuous

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

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

但是,我不清楚如何制止事情。我已经实施了以下代码&它似乎工作,但我很好奇,如果这是停止动画的正确方法,或者是否有另一种首选方法。另外 - 我的轮换一直持续到完成,但是我想知道是否可以在按下按钮时冻结位置的旋转(我在下面的第二次尝试中尝试过.removeAllAnimations(),但是没有#39似乎完全没用。

    @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

3 个答案:

答案 0 :(得分:4)

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

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

    这不能让你轻松控制旋转的速度,而是由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块的动画,则必须捕获停止动画的角度,以便知道从何处重新开始动画。诀窍是抓住m12的{​​{1}}和m11

    CATransform3D

    因此,这会产生:

    angle = atan2(transform.m12, transform.m11)
    
  5. 您可以使用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 } 自行旋转对象,将角度更新为某个计算值。然后停止旋转就像使显示链接无效一样简单,从而将其留在停止时的位置。然后,您只需将显示链接添加回runloop即可恢复动画。

    这种技术可以让你获得很大的控制权,但这种方法最不优雅。

答案 1 :(得分:0)

这第一种方法似乎是合理的。如果要对停止位置进行细粒度控制,则将动画持续时间和旋转弧度除以恒定量。将更频繁地调用您的完成处理程序,以检查是否应继续轮换。

let animationFrequency = 30.0
 func rotateView(targetView: UIView, duration: Double = 1.0 / animationFrequency) {
        UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
            targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi / animationFrequency))
        }) { finished in
            self.rotateView(targetView: targetView, duration: duration)
        }
    }

答案 2 :(得分:0)

我有一个无限的"翻转"标签(它是一个水印),我需要"关闭"显示共享选项时的一个特定标签(或水印)。我通过计时器发生了所有这些 - 在翻转之前保持特定标签观看几秒钟。当你这样做时,关闭计时器很简单。

public class FlipView:UIView {

    private var labelWatermark:FlipLabel!
    private var labelTap:FlipLabel!

    let transitionOptions: UIViewAnimationOptions = [.transitionFlipFromRight, .showHideTransitionViews]
    var isLabel1 = true
    var timer:Timer!

    convenience public init(appName:String) {

        self.init(frame: CGRect.zero)

        labelTap = FlipLabel("Tap to remove", "Watermark")
        self.addSubview(labelTap)

        labelWatermark = FlipLabel("created with", appName)
        self.addSubview(labelWatermark)

        timer = Timer.scheduledTimer(
            timeInterval: 3.0, target: self, selector: #selector(flipViews),
            userInfo: nil, repeats: true)
        timer.fire()

    }

    internal func startFlip() {
        timer = Timer.scheduledTimer(
            timeInterval: 3.0, target: self, selector: #selector(flipViews),
            userInfo: nil, repeats: true)
        timer.fire()
    }

    internal func stopFlip() {
        timer.invalidate()
        UIView.transition(from: labelTap, to: labelWatermark, duration: 1, options: transitionOptions, completion: nil)
        isLabel1 = true
    }

    @objc func flipViews() {
        if (isLabel1) {
            UIView.transition(from: labelWatermark, to: labelTap, duration: 1, options: transitionOptions, completion: nil)
            isLabel1 = false
        } else {
            UIView.transition(from: labelTap, to: labelWatermark, duration: 1, options: transitionOptions, completion: nil)
            isLabel1 = true
        }
    }
}