Multiple Animations For Same UIView in Swift - ios

I am trying to add multiple animations for my image view, but only one of them is animated. Please check the below code. I created scale and rotate animations for my image view but only i see the scale animation when run the below code.
//Rotate animation
let rotation: CABasicAnimation = CABasicAnimation(keyPath:
"transform.rotation.y")
rotation.toValue = 0
rotation.fromValue = 2.61
//Scale animation
let scale: CABasicAnimation = CABasicAnimation(keyPath: "transform.scale")
scale.toValue = 1
scale.fromValue = 0
//Adding animations to group
let group = CAAnimationGroup()
group.animations = [rotation,scale]
group.duration = 0.2
myImage.layer.add(group, forKey: nil)

The rotation occurs but the duration is less to notice
group.duration = 0.2
when changed to 5 seconds see

in your completion you can put your animations, when finish one, wait second.. and etc
let myImage = UIImageView()
UIView.animate(withDuration: 1.0, animations: {
let rotation: CABasicAnimation = CABasicAnimation(keyPath:
"transform.rotation.y")
rotation.toValue = 0
rotation.fromValue = 2.61
}, completion: { (value: Bool) in
UIView.animate(withDuration: 1.0, animations: {
//you can here put your other animation
})
})

Related

Swift 4 Rotation Jumps Back

I'm rotating a the minute pointer on my watch icon, but at the end of the animation, it jumps back to the starting position. How can I have it stay where it stopped?
let rotation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = Double.pi
rotation.duration = 0.5 // or however long you want ...
rotation.isCumulative = true
watchIconMin.layer.add(rotation, forKey: "rotationAnimation")
Use UIView animation if watchIconMin is a subclass or class of UIView.
UIView.animate(withDuration: 0.5, animations: {
self.watchIconMin.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}) { (success) in
self.watchIconMin.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}

Synchronize CABasicAnimation and UIView.animate to be the same

I'm trying to create an animation where when the user drags a UIView then release it, the UIView return to it's initial position. Problems is, I have CAShapeLayer connected to that UIView that I want to stay connected while the UIView return to it's place. CAShapeLayer uses CABasicAnimation and the UIView uses UIView.animate.
How can I get the same timing between the two if I want the same EaseOut deceleration? Right now both animation are way off...
let destinations = self.myAspectDestination[self.viewToDrag.accessibilityIdentifier!] ?? []
for destination in destinations {
if let link: CAShapeLayer = self.myAspects["\(self.viewToDrag.accessibilityIdentifier!):\(destination)"] {
let newPath = UIBezierPath()
newPath.move(to: (self.myPlanets[destination]?.center)!)
newPath.addLine(to: self.originalLocation)
let animation = CABasicAnimation(keyPath: "path")
animation.duration = 1
animation.isRemovedOnCompletion = true
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animation.fillMode = kCAFillModeBoth // keep to value after finishing
animation.fromValue = link.path
animation.toValue = newPath.cgPath
animation.timingFunction = CAMediaTimingFunction(name: "easeInEaseOut")
link.path = newPath.cgPath
link.add(animation, forKey: animation.keyPath)
}
}
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.viewToDrag.center = self.originalLocation
}) { (result) in
self.viewToDrag = nil
}

CALayer resizing back to original size after transform animation

I'm trying to implement two consecutive transformation animations. When the first animation ends, the second animation is called through the completion handler. Because this is a transformation animation, my issue is that when the first animation finishes, the layer resizes back to the original size, and then the second animation begins. I'd like for the second animation to begin with the new layer size after the first transformation animation. This post Objective-C - CABasicAnimation applying changes after animation? says I have to resize/transform the layer before beginning the first animation, so that when the first animation ends, the layer is actually the new size. I've tried to do that by changing the bounds or actually applying the transform to the layer but its still not working.
override func viewDidAppear(_ animated: Bool) {
buildBar()
}
func buildBar(){
progressBar1.bounds = CGRect(x: 0, y: 0, width: 20, height: 5)
progressBar1.position = CGPoint(x: 0, y: 600)
progressBar1.backgroundColor = UIColor.white.cgColor
view.layer.addSublayer(progressBar1)
extendBar1()
}
func extendBar1(){
CATransaction.begin()
let transform1 = CATransform3DMakeScale(10, 1, 1)
let anim = CABasicAnimation(keyPath: "transform")
// self.progressBar1.bounds = CGRect(x: 0, y: 0, width: 200, height: 5)
// self.progressBar1.transform = transform1
anim.isRemovedOnCompletion = false
anim.fillMode = kCAFillModeForwards
anim.toValue = NSValue(caTransform3D:transform1)
anim.duration = 5.00
CATransaction.setCompletionBlock {
self.extendBar2()
}
progressBar1.add(anim, forKey: "transform")
CATransaction.commit()
}
func extendBar2(){
let transform1 = CATransform3DMakeScale(2, 1, 1)
let anim = CABasicAnimation(keyPath: "transform")
anim.isRemovedOnCompletion = false
anim.fillMode = kCAFillModeForwards
anim.toValue = NSValue(caTransform3D:transform1)
anim.duration = 5.00
progressBar1.add(anim, forKey: "transform")
}
Because you are modifying the transform property of the layer in both animation, it will be easier here to use CAKeyframeAnimation, which will handle the chaining of the animations for you.
func extendBar(){
let transform1 = CATransform3DMakeScale(10, 1, 1)
let transform2 = CATransform3DMakeScale(2, 1, 1)
let anim = CAKeyframeAnimation()
anim.keyPath = "transform"
anim.values = [progressBar1.transform, transform1, transform2] // the stages of the animation
anim.keyTimes = [0, 0.5, 1] // when they occurs, 0 being the very begining, 1 the end
anim.duration = 10.00
progressBar1.add(anim, forKey: "transform")
progressBar1.transform = transform2 // we set the transform property to the final animation's value
}
A word about the content of values and keyTimes:
We set the first value to be the current transform of progressBar1. This will make sure we start in the current layer's state.
In keyTimes, we say that at the begining, the first value in the values array should be used. We then say at animation's half time, the layer should be transformed in values second value. Hence, the animation between the initial state and the second one will occurs during that time. Then, between 0.5 to 1, we'll go from tranform1 to transform2.
You can read more about animations in this very nice article from objc.io.
If you really need two distinct animations (because maybe you want to add subviews to progressBar1 in between, here's the code that will do it
func extendBar1() {
CATransaction.begin()
CATransaction.setCompletionBlock {
print("side effects")
extendBar2()
}
let transform1 = CATransform3DMakeScale(5, 1, 1)
let anim = CABasicAnimation(keyPath: "transform")
anim.fromValue = progressBar1.transform
anim.toValue = transform1
anim.duration = 2.00
progressBar1.add(anim, forKey: "transform")
CATransaction.setDisableActions(true)
progressBar1.transform = transform1
CATransaction.commit()
}
func extendBar2() {
CATransaction.begin()
let transform2 = CATransform3DMakeScale(2, 1, 1)
let anim = CABasicAnimation(keyPath: "transform")
anim.fromValue = progressBar1.transform
anim.toValue = transform2
anim.duration = 2.00
progressBar1.add(anim, forKey: "transform")
CATransaction.setDisableActions(true)
progressBar1.transform = transform2
CATransaction.commit()
}
What happens here? Basically, we setup a first "normal animation". So we create the animation, that will modify the presentation layer and set the actual layer to the final first animation's transform.
Then, when the first animation completes, we call extendBar2 which will in turn queue a normal animation.
You also want to call CATransaction.setDisableActions(true) before explicitly updating the transform, otherwise, core animation will create an implicit one, which will override the one created just before.

How to animate view layer shadow with UIViewPropertyAnimator

I know we need to use CABasicAnimation to animate shadow, but I don't know how to integrate UIViewPropertyAnimator with CABasicAnimation.
let shadowAnimation = CABasicAnimation(keyPath: "shadowOpacity")
shadowAnimation.fillMode = kCAFillModeForwards
shadowAnimation.isRemovedOnCompletion = false
shadowAnimation.fromValue = 0.3
shadowAnimation.toValue = 0
shadowAnimation.duration = transitionDuration
animator = UIViewPropertyAnimator(duration: transitionDuration, dampingRatio: 95, animations: {
topShadowContainer.layer.add(shadowAnimation, forKey: "shadowOpacity")
}
I found the solution.

CABasicAnimation autoreverse twice as fast

I'm using this code to add pulsing circle with autoreverse:
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 6
scaleAnimation.repeatCount = 200
scaleAnimation.autoreverses = true
scaleAnimation.fromValue = 0.1
scaleAnimation.toValue = 0.8
scaleAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.42, 0.0, 0.58, 1.0)
animationView.layer.add(scaleAnimation, forKey: "scale")
What I would like to do here is to:
Run animation fromValue = 0.1 toValue = 0.8 at 2x speed,
and go backwards animating it fromValue = 0.8 toValue = 0.1 at 1x speed.
Is there an easy way to achieve this?
You have two ways of doing this:
CAKeyframeAnimation (best choice for you):
Designed specifically for animating a single keyPath with multiple keyframes, with custom timeFunctions on each interval. Just what you need
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 18 // 6 seconds for the first part, 12 for the second
scaleAnimation.repeatCount = 200
scaleAnimation.values = [0.1, 0.8, 0.1] // make sure first and last values are equal in order to get seamless animation
scaleAnimation.keyTimes = [0, 0.333, 1] // keyframes scaled to [0; 1] interval
scaleAnimation.timingFunctions = [
CAMediaTimingFunction(controlPoints: 0.42, 0.0, 0.58, 1.0), //first interval
CAMediaTimingFunction(controlPoints: 0.58, 0.0, 0.42, 1.0) //second interval (reversed)
]
layer.add(scaleAnimation, forKey: nil)
CAAnimationGroup (kind of workaround)
Designed to group animations (perhaps with different keyPaths) for a single layer
let scaleUpAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
//setup first animation as you did
let scaleDownAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
//setup second animation
let groupAnimation = CAAnimationGroup()
groupAnimation.animations = [scaleUpAnimation, scaleDownAnimation]
//setup group if needed
layer.add(groupAnimation, forKey: nil)

Resources