How to Stop an Infinitely Rotating Image? and How Does One Implement Removeallanimations

Proper way to stop an infinitely rotating image? and how does one implement removeAllAnimations?

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

  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?

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

    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).

  3. 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
    }
  4. 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)

    Thus, this yields:

     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. 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.

I need to infinitely rotate a view until told to stop

Actually it would be more easy:

   extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1.0) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat.pi * 2
rotateAnimation.duration = duration
rotateAnimation.repeatCount = Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}

func stopRotating(){
self.layer.sublayers?.removeAll()
//or
self.layer.removeAllAnimations()
}
}

Then for rotating:
yourView.rotate360Degrees()

for stopping:
yourView. stopRotating()

Start/stop image view rotation animations

Swift 3.x

Start Animation

let rotationAnimation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.toValue = NSNumber(value: .pi * 2.0)
rotationAnimation.duration = 0.5;
rotationAnimation.isCumulative = true;
rotationAnimation.repeatCount = .infinity;
self.imageWood?.layer.add(rotationAnimation, forKey: "rotationAnimation")

Stop animation

self.imageWood?.layer.removeAnimation(forKey: "rotationAnimation")

Swift 2.x

Start Animation

let rotationAnimation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.toValue = NSNumber(double: M_PI * 2.0)
rotationAnimation.duration = 1;
rotationAnimation.cumulative = true;
rotationAnimation.repeatCount = .infinity;
self.imageWood?.layer.addAnimation(rotationAnimation, forKey: "rotationAnimation")

Stop animation

self.imageWood?.layer.removeAnimation(forKey: "rotationAnimation")

Rotate UIView infinitely and pause/stop animation, keep state, and continue rotating from current state

Finally got it, with more than one answer on SO, many in objective-c, put them all together in an UIView extension, and even documented:

extension UIView {

/**
Will rotate `self` for ever.

- Parameter duration: The duration in seconds of a complete rotation (360º).
- Parameter clockwise: If false, will rotate counter-clockwise.
*/
func startRotating(duration duration: Double, clockwise: Bool) {
let kAnimationKey = "rotation"
var currentState = CGFloat(0)

// Get current state
if let presentationLayer = layer.presentationLayer(), zValue = presentationLayer.valueForKeyPath("transform.rotation.z"){
currentState = CGFloat(zValue.floatValue)
}

if self.layer.animationForKey(kAnimationKey) == nil {
let animate = CABasicAnimation(keyPath: "transform.rotation")
animate.duration = duration
animate.repeatCount = Float.infinity
animate.fromValue = currentState //Should the value be nil, will start from 0 a.k.a. "the beginning".
animate.byValue = clockwise ? Float(M_PI * 2.0) : -Float(M_PI * 2.0)
self.layer.addAnimation(animate, forKey: kAnimationKey)
}
}

/// Will stop a `startRotating(duration: _, clockwise: _)` instance.
func stopRotating() {
let kAnimationKey = "rotation"
var currentState = CGFloat(0)

// Get current state
if let presentationLayer = layer.presentationLayer(), zValue = presentationLayer.valueForKeyPath("transform.rotation.z"){
currentState = CGFloat(zValue.floatValue)
}

if self.layer.animationForKey(kAnimationKey) != nil {
self.layer.removeAnimationForKey(kAnimationKey)
}

// Leave self as it was when stopped.
layer.transform = CATransform3DMakeRotation(currentState, 0, 0, 1)
}

}

Use it like yourView.startRotating(duration: 1, clockwise: true), later to stop do yourView.stopRotating().

Iphone-How to rotate image continuously for 30 secs and stop at 180 degrees

How about

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
animation.delegate = self;
animation.autoreverses = YES;
animation.repeatDuration = 30;
animation.duration = 1.0;
animation.fromValue = [NSNumber numberWithFloat:-degreeToRadian(180)];
animation.toValue=[NSNumber numberWithFloat:degreeToRadian(180)];
[self.button.layer addAnimation:animation forKey:@"animation"];

then add a delegate method

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
self.button.transform = CGAffineTransformMakeRotation(degreeToRadian(180));
}

I hope this is what you wanted.

Swift Continuous Rotation Animation not so continuous

I'm not sure what's wrong with your code, but I've implemented continuous rotation using this method,

@IBAction func rotateView(sender: UIButton) {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveLinear, animations: { () -> Void in
self.spinningView.transform = self.spinningView.transform.rotated(by: .pi / 2)
}) { (finished) -> Void in
self.rotateView(sender: sender)
}
}

Rotate a view for 360 degrees indefinitely in Swift?

UPDATE Swift 5.x

// duration will helps to control rotation speed
private func rotateView(targetView: UIView, duration: Double = 5) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: .pi)
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}

Swift 2.x way to rotate UIView indefinitely, compiled from earlier answers:

// Rotate <targetView> indefinitely
private func rotateView(targetView: UIView, duration: Double = 1.0) {
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveLinear, animations: {
targetView.transform = CGAffineTransformRotate(targetView.transform, CGFloat(M_PI))
}) { finished in
self.rotateView(targetView, duration: duration)
}
}

UPDATE Swift 3.x

// Rotate <targetView> indefinitely
private 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(M_PI))
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}

Spin Animation Resets to Original Image

The confusing part is that the animation is only changing the layers appearance, not the properties of the layer.

You can think of the animation as overriding the property when it's being rendered.

Sample Image

As you can see, since the model value never changed, when the animation has finished and removes itself, the layer goes back to rendering the actual value of that property.

The wrong way to solve this problem would be to keep the animation around forever. It does produce the correct rendering, but has some unwanted side effects since the model value now "permanently" doesn't match the appearance.

Sample Image

Instead, the better solution is to update the value of the property you are animating as you add the animation to the layer. This might feel like it would break the animation, but if the animation is animating from the old value to the new value, it will "override" whatever the model value is why rendered and when the animation removes the end value will match the model value.

Sample Image


For an more in-depth discussion about where to change the model value when adding the animation and about the way animations are applied, see this question and this blog post of mine.



Related Topics



Leave a reply



Submit