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)
}
}
Rotate UIView 360° forever
Use CABasicAnimation, not view animation, and rotate additively, not to an absolute value. It will also help if you rotate 180 degrees additively forever as this is easier to express.
UIView Infinite 360 degree rotation animation?
Found a method (I modified it a bit) that worked perfectly for me: iphone UIImageView rotation
#import <QuartzCore/QuartzCore.h>
- (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration rotations:(CGFloat)rotations repeat:(float)repeat {
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * rotations * duration ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = repeat ? HUGE_VALF : 0;
[view.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
}
Rotate UIButton 360 degrees
You can use a trick: start rotating with 180 degrees first and then rotate with 360 degrees. Use 2 animations with delay.
Try this.
UIView.animate(withDuration: 0.5) {
self.view.transform = CGAffineTransform(rotationAngle: .pi)
}
UIView.animate(
withDuration: 0.5,
delay: 0.45,
options: UIView.AnimationOptions.curveEaseIn
) {
self.view.transform = CGAffineTransform(rotationAngle: 2 * .pi)
}
Rotate NSView 360 degrees
It works but you have to change two things:
extension NSView {
func rotate360Degrees(duration: CFTimeInterval = 0.5, completionDelegate: AnyObject? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI * 2.0)
rotateAnimation.duration = duration
if let delegate: AnyObject = completionDelegate {
rotateAnimation.delegate = delegate
}
// `addAnimation` will execute *only* if the layer exists
self.layer?.addAnimation(rotateAnimation, forKey: nil)
}
}
- Add a
?
afterself.layer
to allow conditional execution if the layer isn't available.
You could use if let ...
if you prefer:
if let theLayer = self.layer {
theLayer.addAnimation(rotateAnimation, forKey: nil)
}
- Set
wantsLayer
totrue
for your view, to force the view to be layer-backed (views are not automatically layer-backed on OS X).
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:
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()
}
}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: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).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.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
}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
: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
}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.
Animate button 360 clockwise and reverse back
Simple common solution on Swift 5
You can rotate anything
//Clock-wise
rotateAnyView(view: makeOrderButton, fromValue: 0, toValue: 2.0 * Double.pi, duration: 1)
//Reverse
rotateAnyView(view: makeOrderButton, fromValue: 2.0 * Double.pi, toValue:0, duration: 1)
}
func rotateAnyView(view: UIView, fromValue: Double, toValue: Double, duration: Double = 1) {
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.duration = duration
animation.fromValue = fromValue
animation.toValue = toValue
view.layer.add(animation, forKey: nil)
}
Related Topics
Is There Function to Convert Uicolor to Hue Saturation Brightness
How to Find Indexpath for Tapped Button in Tableview Using Seque
Mapkit iOS 9 Detailcalloutaccessoryview Usage
How to Make Uiimageview Automatically Resize to the Size of the Image Loaded
How to Move Cursor from One Text Field to Another Automatically in Swift iOS Programmatically
How to Detect Call Incoming Programmatically
iOS 9.3:An Ssl Error Has Occurred and a Secure Connection to the Server Cannot Be Made
Firebase for iOS, Googleservice-Info.Plist Property "Is_Analytics_Enabled" Set to "No"
Xcode 8 Objective-C Category Warning
Create Uicollectionviewcell Subclass with Xib
Swift If Statement - Multiple Conditions Separated by Commas
Error: Uiview's Window Is Not Equal to Another View's Window
How to Animate a Uibutton Between Two Png Images
Swiftui - Detect When Scrollview Has Finished Scrolling
How to Use Icloud to Sync the Nsuserdefaults Plist File