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.
I found this answer helpful in getting the animation to continue: Rotate a view for 360 degrees indefinitely in Swift?
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)
}
}
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)
}
}
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).
Thanks! JG
There are a couple of ways to do what you're asking about:
If supporting iOS 10+, you can use
UIViewPropertyAnimator
, whose animations you can pause and restart (resuming from where it was paused):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 viewtransform
where it was. Then, to resume the rotation, just add aUIDynamicItemBehavior
for the rotation again: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).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 thepresentationLayer
reported.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 grabm12
andm11
of theCATransform3D
:Thus, this yields:
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.