I have a UIImageView which i want to rotate 180 degrees, taking up 1 second, then i want to wait 1 second at this position, then rotate 180 degrees back to the original position taking up 1 second.
How do i accomplish this? I've tried a 100 approaches and it keeps snapping back instead of rotating back
EDIT: I forgot to add i need this to repeat indefinitely
All you need to do is to create a keyFrame animations. It is designed to chain multiple animations together, and in the first keyframe rotate your UIImageView subclass by PI, and in the second one transform it back to identity.
let rotateForwardAnimationDuration: TimeInterval = 1
let rotateBackAnimationDuration: TimeInterval = 1
let animationDuration: TimeInterval = rotateForwardAnimationDuration + rotateBackAnimationDuration
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: rotateForwardAnimationDuration) {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: rotateBackAnimationDuration) {
self.imageView.transform = .identity
}
})
Outcome:
EDIT:
Here is an example how to make it run indefinitely. I suppose your image is in a viewController, and you hold some reference to your imageView.
So for example, on viewDidAppear, call a function what triggers the animation, and than in the completion block of the animation, just call the same function again.
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.runRotateAnimation()
}
func runRotateAnimation() {
let rotateForwardAnimationDuration: TimeInterval = 1
let rotateBackAnimationDuration: TimeInterval = 1
let animationDuration: TimeInterval = rotateForwardAnimationDuration + rotateBackAnimationDuration
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: rotateForwardAnimationDuration) {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: rotateBackAnimationDuration) {
self.imageView.transform = .identity
}
}) { (isFinished) in
// To loop it continuosly, just call the same function from the completion block of the keyframe animation
self.runRotateAnimation()
}
}
}
You can perform the second animation in the completionHandler presented on UIView.animate
let duration = self.transitionDuration(using: transitionContext)
let firstAnimDuration = 0.5
UIView.animate(withDuration: firstAnimDuration, animations: {
/* Do here the first animation */
}) { (completed) in
let secondAnimDuration = 0.5
UIView.animate(withDuration: secondAnimDuration, animations: {
/* Do here the second animation */
})
}
Now you could have another problem.
If you rotate your view with the CGAffineTransform and for every animation you assign a new object of this type to your view.transform, you will lose the previous transform operation
So, according to this post: How to apply multiple transforms in Swift, you need to concat the transform operation
Example with 2 animation block
This is an example to made a rotation of 180 and returning back to origin after 1 sec:
let view = UIView.init(frame: CGRect.init(origin: self.view.center, size: CGSize.init(width: 100, height: 100)))
view.backgroundColor = UIColor.red
self.view.addSubview(view)
var transform = view.transform
transform = transform.rotated(by: 180)
UIView.animate(withDuration: 2, animations: {
view.transform = transform
}) { (completed) in
transform = CGAffineTransform.identity
UIView.animate(withDuration: 2, delay: 1, options: [], animations: {
view.transform = transform
}, completion: nil)
}
Example of .repeat animation and .autoreverse
The .animate method give you the ability to set some animation options. In particular the structure UIViewAnimationOptions contains:
.repeat, which repeat indefinitely your animation block
.autoreverse, which restore your view to the original status
With this in mind you could do this:
var transform = view.transform.rotated(by: 180)
UIView.animate(withDuration: 2, delay: 0, options: [.repeat, .autoreverse], animations: {
self.myView.transform = transform
})
But you need a delay between the two animations, so you need to do this trick:
Example of recursive animation and a delay of 1 sec
Just create a method inside your ViewController which animate your view. In the last completionHandler, just call the method to create a infinite loop.
Last you need to call the method on viewDidAppear to start the animation.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.animation()
}
func animation() {
var transform = view.transform
transform = transform.rotated(by: 180)
UIView.animate(withDuration: 2, delay: 0, options: [], animations: {
self.myView.transform = transform
}) { bool in
transform = CGAffineTransform.identity
UIView.animate(withDuration: 2, delay: 1, options: [], animations: {
self.myView.transform = transform
}, completion: { bool in
self.animation()
})
}
}
Related
My code is using a simple loop animation that is not stopping after the 5 second duration. All I want to do is have a second button that stops the animation. As you can see in my code, circle.stopAnimating() has no effect.
class VET: UIViewController {
#IBOutlet weak var circle: UIImageView!
var duration = 5
#IBAction func start(_ sender: Any) {
UIView.animate(withDuration: TimeInterval(duration), delay: 0.5, options: [.repeat, .autoreverse, .curveEaseIn, .curveEaseOut], animations: { () -> Void in
let scale = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.circle.transform = scale
print("animation")
}, completion: { _ in
//if finished { or (finished: Bool)
// if isAnimating {
if self.circle.isAnimating {
self.circle.stopAnimating()
print("is animating -- stop animating")
} else {
self.circle.startAnimating()
print("start animating")
}
})
}
#IBAction func stop() {
circle.stopAnimating()
}
startAnimating and stopAnimating for change images of imageview in some interval like GIF. Here you used animation block with repeat and the completion block will only get called when the animation is interrupted. For example it gets called when the app goes in the background and comes back to the foreground again.
To stop animation after some interval you have to call forcefully removeAllAnimations after that interval. Please refer following code:
#IBOutlet weak var circle: UIImageView!
var duration = 10
func start() {
UIView.animate(withDuration: TimeInterval(duration), delay: 0.5, options: [.repeat, .autoreverse, .curveEaseIn, .curveEaseOut], animations: { () -> Void in
let scale = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.circle.transform = scale
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.duration * 1000)) {
// Code
self.circle.layer.removeAllAnimations()
}
print("animation")
}, completion: { _ in
let scale = CGAffineTransform(scaleX: 1.0, y: 1.0)
self.circle.transform = scale
})
}
You are using wrong approach to use animations if you need more control over it.
Instead use UIViewPropertyAnimator
The alternate property animator initializer gives you back an animator object that you need to start:
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.redSquare.backgroundColor = .green
}
animator.startAnimation()
Similarly you can use for stopping animations related to the animator
animator.stopAnimation()
My code below is a button that when hit applies animation. Right now there are two animations. I would like to do a sequence the animations meaning have the 2nd animation not start until the first animation has completed.
var speed: CGFloat = 5.3 // speed in seconds
#IBAction func press(_ sender: Any) {
self.theTextView.resignFirstResponder()
UIView.animate(withDuration: TimeInterval(speed), animations: {
////1st action[
self.theTextView.contentOffset = .zero
self.theTextView.setContentOffset(.zero, animated: true)]
/////2nd action[
self.theTextView.contentOffset = CGPoint(x: 0, y: self.theTextView.contentSize.height)]
}, completion: nil)
}}
The easiest way of doing that would be using the completion block of the animate(withDuration:animations:completion:) method. The completion block is executed when the animation sequence ends. In your case, it would look like this :
UIView.animate(withDuration: 6.0, animations: {
// First animation goes here
self.theTextView.contentOffset = CGPoint.zero
}, completion: { (finished) in
// This completion block is called when the first animation is done.
// Make sure the animation was not interrupted/cancelled :
guard finished else {
return
}
UIView.animate(withDuration: 1.0, animations: {
// And the second animation goes here
self.theTextView.contentOffset = CGPoint(x: 0, y: self.theTextView.contentSize.height)
})
})
There is also convenient way to make sequence of concurrent animations by using animateKeyframes:
UIView.animateKeyframes(withDuration: 1, delay: 0, options: .calculationModeCubic, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.3, animations: {
self.someView.alpha = 0.5
self.view.layoutIfNeeded()
})
//second animation
UIView.addKeyframe(withRelativeStartTime: 0.4, relativeDuration: 0.3, animations: {
self.someView.alpha = 1
self.view.layoutIfNeeded()
})
}, completion: { (finish) in
// Do something on animation complete
})
Here is my code. Intent is to continuously rotate the UIImageView named swirls[l]. However, there is a small pause between every rotation start/end. I have gone through every single animation tutorial but cant figure out what the mistake is?
let fullRotation = CGFloat(M_PI * 2)
let duration = 2.0
let delay = 0.0
let options = UIViewKeyframeAnimationOptions.Repeat | UIViewKeyframeAnimationOptions.CalculationModeLinear
UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: {
swirls[l].transform = CGAffineTransformMakeRotation(1/3 * fullRotation)
})
UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/3, animations: {
swirls[l].transform = CGAffineTransformMakeRotation(2/3 * fullRotation)
})
UIView.addKeyframeWithRelativeStartTime(2/3, relativeDuration: 1/3, animations: {
swirls[l].transform = CGAffineTransformMakeRotation(3/3 * fullRotation)
})
}, completion: {finished in
})
EDIT: I see that it has been suggested that a previous solution is available, but it simply does not work for continuous uninterrupted rotation. The only trick that worked for me is the answer that I chose below. Thanks
Try below extension for swift 4.
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 3) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(Double.pi * 2)
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = duration
rotateAnimation.repeatCount=Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
}
For start rotation.
MyView.rotate360Degrees()
And for Stop.
MyView.layer.removeAllAnimations()
You can use UIButton, UILabel and many more.
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)
}
}
If you want to continuous rotate Button or View and stop that rotation whenever you want then you can try this:
For start rotation:
#IBOutlet weak var RotateBtn: UIButton!
var timeTimer: Timer?
viewDidLoad()
{
self.rotateView()
timeTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(self.rotateView), userInfo: nil, repeats: true)
}
func rotateView()
{
UIView.animate(withDuration: 0.5, delay: 0, options: .curveLinear, animations: { () -> Void in
self.RotateBtn.transform = self.RotateBtn.transform.rotated(by: CGFloat(M_PI_4))
})
}
If you want to stop rotation then use:
#IBAction func StopBtn_Pressed(_ sender: AnyObject)
{
timeTimer?.invalidate()
self.RecordBtn.layer.removeAllAnimations()
}
I want to rotate an image view for 360 degrees indefinitely.
UIView.animate(withDuration: 2, delay: 0, options: [.repeat], animations: {
self.view.transform = CGAffineTransform(rotationAngle: 6.28318530717959)
}, completion: nil)
How can I do it?
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)
}
}
Swift 3.0
let imgViewRing = UIImageView(image: UIImage(named: "apple"))
imgViewRing.frame = CGRect(x: 0, y: 0, width: UIImage(named: "apple")!.size.width, height: UIImage(named: "apple")!.size.height)
imgViewRing.center = CGPoint(x: self.view.frame.size.width/2.0, y: self.view.frame.size.height/2.0)
rotateAnimation(imageView: imgViewRing)
self.view.addSubview(imgViewRing)
This is the animation logic
func rotateAnimation(imageView:UIImageView,duration: CFTimeInterval = 2.0) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(.pi * 2.0)
rotateAnimation.duration = duration
rotateAnimation.repeatCount = .greatestFiniteMagnitude
imageView.layer.add(rotateAnimation, forKey: nil)
}
You can check output in this link
Use this extension to rotate UIImageView 360 degrees.
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1.0, completionDelegate: AnyObject? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI)
rotateAnimation.duration = duration
if let delegate: CAAnimationDelegate = completionDelegate as! CAAnimationDelegate? {
rotateAnimation.delegate = delegate
}
self.layer.addAnimation(rotateAnimation, forKey: nil)
}
}
Than to rotate UIImageView simply use this method
self.YOUR_SUBVIEW.rotate360Degrees()
This one work for me in Swift 2.2:
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = 360 * CGFloat(M_PI/180)
let innerAnimationDuration : CGFloat = 1.0
rotationAnimation.duration = Double(innerAnimationDuration)
rotationAnimation.repeatCount = HUGE
self.imageView.addAnimation(rotationAnimation, forKey: "rotateInner")
I would stick it in a function like rotateImage() and in the completion code just call rotateImage() again. I think you should use M_PI (or the swift equivalent) for the rotation amount, though.
Updated for Swift 3:
I made an extension to UIView and included the following function within:
func rotate(fromValue: CGFloat, toValue: CGFloat, duration: CFTimeInterval = 1.0, completionDelegate: Any? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: >"transform.rotation")
rotateAnimation.fromValue = fromValue
rotateAnimation.toValue = toValue
rotateAnimation.duration = duration
if let delegate: Any = completionDelegate {
rotateAnimation.delegate = delegate as? CAAnimationDelegate
}
self.layer.add(rotateAnimation, forKey: nil)
}
You can then call the function via (on a UIView I made into a button for example) :
monitorButton.rotate(fromValue: 0.0, toValue: CGFloat(M_PI * 2), completionDelegate: self)
Hope this helps!
I think what you really want here is to use a CADisplayLink. Reason being that this would be indefinitely smooth versus using completion blocks which may cause slight hiccups and are not as easily cancelable. See the following solution:
var displayLink : CADisplayLink?
var targetView = UIView()
func beginRotation () {
// Setup display link
self.displayLink = CADisplayLink(target: self, selector: #selector(onFrameInterval(displayLink:)))
self.displayLink?.preferredFramesPerSecond = 60
self.displayLink?.add(to: .current, forMode: RunLoop.Mode.default)
}
func stopRotation () {
// Invalidate display link
self.displayLink?.invalidate()
self.displayLink = nil
}
// Called everytime the display is refreshed
#objc func onFrameInterval (displayLink: CADisplayLink) {
// Get frames per second
let framesPerSecond = Double(displayLink.preferredFramesPerSecond)
// Based on fps, calculate how much target view should spin each interval
let rotationsPerSecond = Double(3)
let anglePerSecond = rotationsPerSecond * (2 * Double.pi)
let anglePerInterval = CGFloat(anglePerSecond / framesPerSecond)
// Rotate target view to match the current angle of the interval
self.targetView.layer.transform = CATransform3DRotate(self.targetView.layer.transform, anglePerInterval, 0, 0, 1)
}
Avoiding the completion closure with recursive calls!
Bit late to this party, but using UIView keyFrame animation & varying stages of rotation for each keyFrame, plus setting the animation curve works nicely. Here's an UIView class function -
class func rotate360(_ view: UIView, duration: TimeInterval, repeating: Bool = true) {
let transform1 = CGAffineTransform(rotationAngle: .pi * 0.75)
let transform2 = CGAffineTransform(rotationAngle: .pi * 1.5)
let animationOptions: UInt
if repeating {
animationOptions = UIView.AnimationOptions.curveLinear.rawValue | UIView.AnimationOptions.repeat.rawValue
} else {
animationOptions = UIView.AnimationOptions.curveLinear.rawValue
}
let keyFrameAnimationOptions = UIView.KeyframeAnimationOptions(rawValue: animationOptions)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: [keyFrameAnimationOptions, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.375) {
view.transform = transform1
}
UIView.addKeyframe(withRelativeStartTime: 0.375, relativeDuration: 0.375) {
view.transform = transform2
}
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
view.transform = .identity
}
}, completion: nil)
}
Looks pretty gnarly, with the weird rotation angles, but as the op & others have found, you can't just tell it to rotate 360
Try this one it works for me, i am rotating image for once
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {
self.imgViewReload.transform = CGAffineTransformRotate(self.imgViewReload.transform, CGFloat(M_PI))
}, completion: {
(value: Bool) in
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {
self.imgViewReload.transform = CGAffineTransformIdentity
}, completion: nil)
})
you can use this function ... just give it the view and the duration
it has two animations the the first rotates the view 180° and the other one rotates it to 360° then the function calls itself which allows it to continue the rotation animation infinitely
func infinite360Animation(targetView: UIView, duration: Double) {
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi )
} completion: { (_) in
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi * 2)
} completion: { (_) in
self.infinite360Animation(targetView: targetView, duration: duration)
}
}
}
You can then call the function like this
infinite360Animatio(targetView: yourView, duration: 3)
On your tabBarController, make sure to set your delegate and do the following didSelect method below:
func tabBarController(_ tabBarController: UITabBarController,
didSelect viewController: UIViewController) {
if let selectedItem:UITabBarItem = tabBarController.tabBar.selectedItem {
animated(tabBar: tabBarController.tabBar, selectedItem: selectedItem)
}
}
fileprivate func animated(tabBar: UITabBar, selectedItem:UITabBarItem){
if let view:UIView = selectedItem.value(forKey: "view") as? UIView {
if let currentImageView = view.subviews.first as? UIImageView {
rotate(imageView: currentImageView, completion: { (completed) in
self.restore(imageView: currentImageView, completion: nil)
})
}
}
}
fileprivate func rotate(imageView:UIImageView, completion:((Bool) ->Void)?){
UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
imageView.transform = CGAffineTransform.init(rotationAngle: CGFloat.pi)
}, completion: {
(value: Bool) in
completion?(value)
})
}
fileprivate func restore(imageView:UIImageView, completion:((Bool) ->Void)?){
UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
imageView.transform = CGAffineTransform.identity
}, completion: {
(value: Bool) in
completion?(value)
})
}
I have an object I'm trying to animate to roll across the screen. I don't know how to change the position of the object and make it spin more than it is already. This is what I have so far:
#IBOutlet weak var quarterImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
UIView.animateWithDuration(8.0, animations: {
let angle = CGFloat(-4000)
let rotate = CGAffineTransformMakeRotation(angle)
self.quarterImage.transform = CGAffineTransformMakeRotation(angle)
})
}
Any help would be great. Thanks!
I am using some bouncing animation of a smiley face image which jumps repeatedly.
Here is the code so that you can have idea about changing position.
For rotation I guess you need to play around with different PI values of angle to get smooth rotation.
Swift 2
UIView.animateWithDuration(0.6, delay: 0.3, options: [UIViewAnimationOptions.Autoreverse, UIViewAnimationOptions.Repeat, UIViewAnimationOptions.CurveEaseOut], animations:
{
self.m_smileyImgView?.transform.ty -= 30;
},completion: nil);
Swift 3,4,5
UIView.animate(withDuration: 0.6, delay: 0.3, options: [UIView.AnimationOptions.autoreverse, UIView.AnimationOptions.repeat, UIView.AnimationOptions.curveEaseOut], animations:
{
self.m_smileyImgView?.transform.ty -= 30;
},completion: nil);
CGAffineTransformMakeRotation can be a little tricky
First of all, it expects degrees in radians
Second of all, values over 2PI doesn't matter (0 = 2PI = 6PI = 10000PI)
Here is an example function for rotating an image 360 degrees
func rotateImage(times times:Int) {
if times == 0 { return }
UIView.setAnimationCurve(.Linear)
UIView.animateKeyframesWithDuration(1, delay: 0, options: .CalculationModeLinear, animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: {
self.imageView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 2/3))
})
UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/3, animations: {
self.imageView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 4/3))
})
UIView.addKeyframeWithRelativeStartTime(2/3, relativeDuration: 1/3, animations: {
self.imageView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 2))
})
}) { (_) in
self.rotateImage(times: times - 1)
}
}
If you want to move the object while you rotate, you can change its position by adding this keyframe
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1.0, animations: {
self.imageView.center.x += 50
})