My code is below. How can I speed slow when animation has stop?
extension UIView{
func rotate() {
let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.fromValue = 0.0
rotation.toValue = 25
rotation.duration = 1.5
rotation.isCumulative = true
rotation.repeatCount = 1
self.layer.add(rotation, forKey: "rotationAnimation")
}
}
Please find following details and add below line in your code,
rotation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
EaseInOut An ease-in ease-out curve causes the animation to begin
slowly, accelerate through the middle of its duration, and then slow
again before completing. This is the default curve for most
animations.
EaseIn An ease-in curve causes the animation to begin slowly, and then
speed up as it progresses.
EaseOut An ease-out curve causes the animation to begin quickly, and
then slow down as it completes.
Hope this helps to you and let me know in case of any queries.
You can use self.layer.speed to decrease animation speed smoothly
You can do it like this
Note: You need to do some modification here it is not tested in xcode
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
self.layer.timeOffset = self.layer.convertTime(CACurrentMediaTime(), from: nil)
if self.layer.speed == 0 { timer.invalidate() }
self.layer.beginTime = CACurrentMediaTime()
self.layer.speed -= 0.5
}
timer.fire()
Hope it is helpful
Related
Goal:
First animation runs for a duration of 1.0
At 1.0, the second animation runs (while the first animation autoreverses back to its starting point)
At 2.0, the first animation is back at its starting point, the second animation has completed, and the first animation repeats
At 3.0, the second animation runs as the first animation concludes its second run
My code:
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.fromValue = 1.0
animation.toValue = 1.5
animation.duration = 1.0
animation.autoreverses = true
animation.repeatCount = .greatestFiniteMagnitude
image1.layer.add(animation, forKey: animation.keyPath)
let animation2 = CABasicAnimation(keyPath: "transform.scale")
animation2.fromValue = 1.0
animation2.toValue = 2.0
animation2.duration = 1.0
animation2.fillMode = .forwards
let animation2b = CABasicAnimation(keyPath: "opacity")
animation2b.fromValue = 1.0
animation2b.toValue = 0.0
animation2b.duration = 1.0
animation2b.fillMode = .forwards
let animationGroup = CAAnimationGroup()
animationGroup.animations = [animation2, animation2b]
animationGroup.duration = 2.0
animationGroup.beginTime = 1.0
animationGroup.repeatCount = .greatestFiniteMagnitude
image2.layer.add(animationGroup, forKey: "scaleAndFade")
The goal is to start the 2nd animation 1.0 after the 1st animation. And since the animation group has a duration of 2.0 while the animations within it have a duration of only 1.0, the animation would start at 1.0, end at 2.0, and then not repeat again until 3.0
The two animations sometimes match up, but not on every build. Is there a more surefire way of starting the second animation to begin exactly at the end of the initial animation's first completed animation? So that they'll be in sync from that point on. Thanks for any help!
I’m a little unclear on what we’re trying to achieve, but my sense is that the idea is that we have two views with repeating animations that need to be coordinated. To demonstrate an example of how this might be done, I chose to make both animations be a scale-up followed by a scale-down:
The animated gif here comes to an end after a few repetitions, but in actual fact the repetition just goes on forever.
That was simply achieved as follows:
func step1() {
UIView.animate(withDuration: 1, animations: {
self.v1.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
self.v2.transform = .identity
}) { _ in
DispatchQueue.main.async {
self.step2()
}
}
}
func step2() {
UIView.animate(withDuration: 1, animations: {
self.v1.transform = .identity
self.v2.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}) { _ in
DispatchQueue.main.async {
self.step1()
}
}
}
The point is that the stages of the animation can never get out of sync between the two views, because each stage involves both views and follows the previous stage. So I’m pretty sure you can adapt that approach to fit your animation.
I like #matt's answer and always appreciate their input but since I was trying to use CAAnimation (specifically I wanted to use CAKeyframeAnimation), I ended up nesting two CATransactions using CATransaction.begin() and CATransaction.setCompletionBlock to start one animation exactly at the end of the other and then to recursively call the function repeatedly.
CATransaction documentation
I created a function with Core Animation that animate the layer height from 0 to N and it's delayable.
extension CALayer {
func animate(to height: CGFloat, duration: Double, delay: Double) {
let animation: CABasicAnimation = .init(keyPath: "bounds.size.height")
animation.fromValue = 0
animation.toValue = height
animation.beginTime = CACurrentMediaTime() + delay
animation.duration: duration
animation.timingFunction = .init(name: kCAMediaTimingFunctionEaseInEaseOut)
// I want to improvement this part.
//
// self.isHidden = true
//
// DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
// self.isHidden = false
// }
self.bounds.size.height = height
self.add(animation, forKey: "bounds.size.height")
}
}
And the layer does transform well during animation, but it returns to original height before begin time and after finish. So I had to set isHidden of layer according to time to delay.
But I don't think it's a safe way. So I want to improvement that code.
What do you usually do in this case?
Try setting animation.fillMode = .backwards. I think that will make the animation will apply its fromValue to the layer until the animation's beginTime is reached.
Basically I just add a simple rotate animation on an imageView which is a subview of UICollectionViewCell, the animation works fine, but when I scroll the collection view and scroll back, the animation just stoped. How to solve this problem?
This is what I added to the imageView.
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = Double.pi
rotationAnimation.duration = 2.0
rotationAnimation.repeatCount = .infinity
view.layer.add(rotationAnimation, forKey: nil)
To avoid scrolling's influence, need to use Runloop and its commonModes, making animation with CADisplayLink can do it:
private var _displayLinkForRotation:CADisplayLink?
var displayLinkForRotation:CADisplayLink {
get{
if _displayLinkForRotation == nil {
_displayLinkForRotation = CADisplayLink(target: self, selector: #selector(excuteAnimations))
_displayLinkForRotation?.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)
}
return _displayLinkForRotation!
}
}
func excuteAnimations() {
//This function will be called 60 times per second.
//According to your question, you have 2 seconds to rotate the view to 180 angle. So we rotate 90 angle per second here.
//self.view could be replaced by another view you want to rotate.
self.view.transform = self.view.transform.rotated(by: 90.0 / 60.0 / 180.0 * CGFloat.pi)
let angle = atan2(self.view.transform.b, self.view.transform.a) * (45.0/atan(1.0))
if (round(angle) >= 180.0) { //Stop when rotated 180 angle
self.displayLinkForRotation.isPaused = true
}
}
To start animation:
self.displayLinkForRotation.isPaused = false
To destroy it:
self.displayLinkForRotation.invalidate()
_displayLinkForRotation = nil
Because you have not added key for animation, which you used when initialising rotationAnimation
change line
view.layer.add(rotationAnimation, forKey: nil)
to
view.layer.add(rotationAnimation, forKey: "transform.rotation")
Hope it helps
I have an image which is a square and I know how to make it spin. But not sure how to make it spin exactly like this animation here:
Notice how it spins... then stops a little... then spins again... and so on.
What I have just is a basic spin but doesn't look like the gif above:
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 3) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI * 2)
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = duration
rotateAnimation.repeatCount=Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
}
Any thoughts on how this is done?
You need to use a timing function that accelerates and decelerates, also known as an ease-in-ease-out timing function.
I've modified your function to use Core Animation's standard ease-in-ease-out timing function:
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 0.8) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
let radians = CGFloat.pi / 4
rotateAnimation.fromValue = radians
rotateAnimation.toValue = radians + .pi
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = duration
rotateAnimation.repeatCount=Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
}
Result:
Note that in your image, it looks like the box pauses every 180 degrees, so I've changed the function to rotate by only 180 degrees. Since the box has 90 degree radial symmetry, it still looks like it goes all the way around, with pauses at 180 and 360 degrees.
If you need to animate an image without any radial symmetry, you'll need to use a CAKeyframeAnimation to achieve the ease-in-ease-out at both 180 and 360 degrees.
Similar effect can also be achieved with a repeated animation with a delay.
UIView.animate(withDuration: 1,
delay: 0.2,
options: [.repeat],
animations: { imageView.transform = imageView.transform.rotated(by: CGFloat.pi) },
completion: nil)
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()
}
}