Spring animation for rotation that ends where it began - ios

I'd like to create a CASpringAnimation for rotation that ends where it began. Here's some working code that doesn't end where it began (but otherwise is what I'm looking for):
func spring(view: UIView) -> CASpringAnimation{
let animation = CASpringAnimation(keyPath: "transform")
animation.damping = 0.5
animation.initialVelocity = 70
animation.mass = 0.04
animation.stiffness = 12
animation.duration = 1.2
animation.toValue = CATransform3DRotate(CATransform3DMakeAffineTransform(view.transform),
CGFloat(M_PI)/32, // Must be non-zero 😕
0, 0, 1)
return animation
}
And here's how you'd use it:
let animation = spring(view: viewToAnimate)
viewToAnimate.layer.add(animation, forKey: nil)
When I set the angle (beside the comment in CATransform3DRotate) to 0, the animation doesn't work! Any ideas on how to get a spring rotation with the same start and end angles? Thanks for reading.

By adding a CABasicAnimation that animates to 0° over the same time duration, and also by tweaking the spring animation parameters, I've got something that looks pretty good (tested for iOS 10):
#IBAction func springButtonTapped(_ sender: UIButton) { // Attach to the view you'd like animated
let springAnim = spring(view:sender)
sender.layer.add(springAnim, forKey: nil)
let correctionAnim = correct(view:sender)
sender.layer.add(correctionAnim, forKey: nil)
}
func correct(view: UIButton) -> CABasicAnimation{
let animation = CABasicAnimation(keyPath: "transform")
animation.duration = 0.3
animation.toValue = CATransform3DRotate(CATransform3DMakeAffineTransform(view.transform),
0,
0, 0, 1)
return animation
}
func spring(view: UIView) -> CASpringAnimation{
let animation = CASpringAnimation(keyPath: "transform")
animation.damping = 0.0
animation.initialVelocity = 80
animation.mass = 0.04
animation.stiffness = 50
animation.duration = 0.3
animation.toValue = CATransform3DRotate(CATransform3DMakeAffineTransform(view.transform),
CGFloat(M_PI)/64,
0, 0, 1)
return animation
}

Related

Synchronously animate CALayer property and UIView with UIViewPropertyAnimator

I'm using a UIViewPropertyAnimator to animate the position of a view. But, I also want to animate CALayer properties like borderColor along with it. Currently, it doesn't animate and instantly changes at the start, like this:
Here's my code:
class ViewController: UIViewController {
var animator: UIViewPropertyAnimator?
let squareView = UIView(frame: CGRect(x: 0, y: 40, width: 80, height: 80))
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(squareView)
squareView.backgroundColor = UIColor.blue
squareView.layer.borderColor = UIColor.green.cgColor
squareView.layer.borderWidth = 6
animator = UIViewPropertyAnimator(duration: 2, curve: .easeOut, animations: {
self.squareView.frame.origin.x = 100
self.squareView.layer.borderColor = UIColor.red.cgColor
})
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.animator?.startAnimation()
}
}
}
I looked at this question, How to synchronously animate a UIView and a CALayer, and the answer suggested using "an explicit animation, like a basic animation." It said,
If the timing function and the duration of both animations are the same then they should stay aligned.
However, if I use CABasicAnimation, I lose all the benefits of UIViewPropertyAnimator, like timing and stopping in the middle. I will also need to keep track of both. Is there any way to animate CALayer properties with UIViewPropertyAnimator?
CABasicAnimation has timing as well as keyframe animation to stop in the middle. But, to replicate your animation above:
squareView.layer.transform = CATransform3DMakeTranslation(100, 0, 0)
let fromValue = squareView.transform
let toValue = 100
let translateAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.transform))
translateAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
translateAnimation.fromValue = fromValue
translateAnimation.toValue = toValue
translateAnimation.valueFunction = CAValueFunction(name: .translateX)
let fromColor = squareView.layer.borderColor
let toColor = UIColor.red.cgColor
let borderColorAnimation = CABasicAnimation(keyPath: "borderColor")
borderColorAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
borderColorAnimation.fromValue = fromColor
borderColorAnimation.toValue = toColor
let groupAnimation = CAAnimationGroup()
groupAnimation.animations = [translateAnimation, borderColorAnimation]
groupAnimation.duration = 5
squareView.layer.add(groupAnimation, forKey: nil)

How do i keep on rotating an image until a response from a server in swift

I am making an async call whenever a button is clicked now I want an image which is like a refresh icon to be rotating or spinning until I get a response from my server. What i have now only rotates pi that's 180 and when the response arrives too the image doesn't reset. Have pasted my sample code below:
//When the update button is clicked
#objc func updateHome(){
UIView.animate(withDuration: 1) {
self.updateIcon.transform = CGAffineTransform(rotationAngle: .pi)
}
getBalances()
}
//Inside getBalances function
DispatchQueue.main.async {
if responseCode == 0 {
let today = self.getTodayString()
print("Time: \(today)")
UIView.animate(withDuration: 1) {
self.updateIcon.transform = CGAffineTransform(rotationAngle: .pi)
}
}
This code rotates your UIImageView indefinitely using CABasicAnimation:
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(.pi * 2.0)
rotateAnimation.duration = 1.0 // Change this to change how many seconds a rotation takes
rotateAnimation.repeatCount = Float.greatestFiniteMagnitude
updateIcon.layer.add(rotateAnimation, forKey: "rotate")
And when you get your signal from your server you can call
updateIcon.layer.removeAnimation(forKey: "rotate")
to stop the animation
For continuous rotation, you can use CAKeyframeAnimation too like below.
#objc func updateHome() {
let animation = CAKeyframeAnimation(keyPath: "transform.rotation.z")
animation.duration = 1.0
animation.fillMode = kCAFillModeForwards
animation.repeatCount = .infinity
animation.values = [0, Double.pi/2, Double.pi, Double.pi*3/2, Double.pi*2]
let moments = [NSNumber(value: 0.0), NSNumber(value: 0.1),
NSNumber(value: 0.3), NSNumber(value: 0.8), NSNumber(value: 1.0)]
animation.keyTimes = moments
self.updateIcon.layer.add(animation, forKey: "rotate")
getBalances()
}
In your Async method (inside)DispatchQueue.main.async, you can stop like below. This will keep you to the original position of the image.
self.updateIcon.layer.removeAllAnimations()
One way you can handle this is to add an instance variable that tracks when the work is completed:
var finished: Bool = false
Then write a function that rotates the image and uses the completion handler to call itself to continue rotating:
func rotateImage() {
UIView.animate(withDuration: 1,
delay: 0.0,
options: .curveLinear
animations: {
self.updateIcon.transform = CGAffineTransform(rotationAngle: .pi)
},
completion: { completed in
if !self.finished {
self.rotateImage()
}
}
}
}
If you want to stop the animation instantly, you should set finished = true and call updateIcon.layer.removeAllAnimations()

Flipping x animation with repetition after a certain duration

I was trying to do a "flip x animation" for a UIButton. I have seen some documentation on how to do this animation but i couldn't find how to do it with repetition. For example, i want to flip the image of the UIButton and change it after a time interval of 5 sec with repetition.
Any help? Thanks.
You can use CAAnimationGroup to group an array of animations. Total animation for whole animationGroup is 6.0, flip animation takes place for 1.0 second. so remaining 5.0 seconds act as delay here.
EDIT: CATransaction.setCompletionBlock can be used but it will only call completion after all animations are completed. So now the flip will occur only for one time and on completion you will receive completion call, and update image view and initiate flip animation again.
Like this
imageView.flip {
// update imageView and call it again.
}
extension UIView {
func flip(completion: #escaping ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock {
// completion code here
completion()
}
let animationGroup = CAAnimationGroup()
animationGroup.duration = 6.0
animationGroup.repeatCount = 1
let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
let animation = CABasicAnimation(keyPath: "transform")
animation.duration = 1.0
animation.autoreverses = true
animation.fromValue = CATransform3DIdentity
animation.toValue = NSValue(caTransform3D: CATransform3DMakeRotation(CGFloat(M_PI), 0, 1, 0))
animation.timingFunction = easeInOut
animationGroup.animations = [animation]
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
layer.add(animationGroup, forKey: "flip")
CATransaction.commit()
}
}

Swift Image Rotation showing 50% image

I'm new in swift and iPhone development. Recently I'm working on a topic where I have to rotate the image vertically.
I've almost solved the issue but problem is my image is rotating but while it's rotating it's showing 50% image. Here is the link of the issue in video. http://screencast.com/t/VI4yZ0a8Wr
Also here the code i'm using
func animationRotationEffect(view:UIView,animationTime:Float)
{
UIView.animateWithDuration(NSTimeInterval(animationTime), animations: { () -> Void in
/* previously used
same result produced as below
var animation:CABasicAnimation = CABasicAnimation(keyPath: "transform")
animation.toValue = NSValue(CATransform3D:CATransform3DMakeRotation(CGFloat(M_PI), 1, 0, 0))
animation.duration = CFTimeInterval(animationTime)
animation.cumulative = false
animation.repeatCount = 1
view.layer.addAnimation(animation, forKey: nil)
*/
let animate = CABasicAnimation(keyPath: "transform.rotation.x")
animate.duration = CFTimeInterval(animationTime)
animate.repeatCount = Float.infinity
animate.fromValue = 0.0
animate.toValue = Float(M_PI * 2.0)
view.layer.addAnimation(animate, forKey: nil)
})
}
It would be very helpful if anyone can help me on this.
Thanks in advance,
Nixon
Maybe it's not an animation problem, try setting the aspect and content mode of your UIImageView to this
imageView.contentMode = UIViewContentMode.ScaleAspectFit
More info about UIViewContentMode enumeration here

Basic Core Animation - moving image

NO UIVIEW ANIMATIONS, I'M LEARNING CORE ANIMATION
When I run this code, the top image is not on the screen. Then 4 seconds later, it reappears and does as expected. Not sure why. What I want is for the top image to be on the screen when the app launches, and then 4 seconds later for the top image to move up and out of the screen.
#IBOutlet var top: UIImageView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let openTop = CABasicAnimation(keyPath: "position.y")
openTop.fromValue = self.top.frame.origin.y
openTop.toValue = -self.view.bounds.size.height
openTop.duration = 1.0
openTop.beginTime = CACurrentMediaTime() + 4
self.top.layer.addAnimation(openTop, forKey: nil)
self.top.layer.position.y = -self.view.bounds.size.height
}
Any thoughts?
Here's what I came up with:
let l = top.layer!
let startingPosition = l.position
l.position = CGPointMake( CGRectGetMidX( self.view.bounds ), -l.frame.height * 0.5 )
l.addAnimation({
let anim = CABasicAnimation( keyPath: "position" )
anim.fromValue = NSValue( CGPoint:startingPosition )
anim.beginTime = CACurrentMediaTime() + 4.0
anim.fillMode = kCAFillModeBoth
return anim
}(), forKey: nil)
so you want the image to be on-screen at the start, and then 4 seconds later to fly off the top? You can do this by grouping animations together
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
// keep a track of where we want it to be
let y = self.top.layer.position.y
// move it out of the way - this where it will end up after the animation
self.top.layer.position.y = -self.view.bounds.size.height
// do nothing, other than display the image for 4 seconds
let doNothing = CABasicAnimation(keyPath: "position.y")
doNothing.fromValue = y
doNothing.toValue = y
doNothing.duration = 4.0
// zip it away
let openTop = CABasicAnimation(keyPath: "position.y")
openTop.fromValue = y
openTop.toValue = -self.view.bounds.size.height
openTop.duration = 1.0
openTop.beginTime = doNothing.beginTime + doNothing.duration
// create the group
let group = CAAnimationGroup()
group.duration = 5.01
group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
group.removedOnCompletion = false
group.fillMode = kCAFillModeForwards
group.animations = [doNothing, openTop]
self.top.layer.addAnimation(group, forKey: nil)
}

Resources