I want to achieve pulsation effect for the button, and hence need to repeat the spring effect several number of times, the issue is that I can't find any information about what parameters to provide and how to do it
let btnView = sayWordBtn.viewWithTag(0)
btnView.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
let mass: CGFloat = 2.0 // weight of the object
let stiffness: CGFloat = 25.0 //elasticity
let damping: CGFloat = 2*sqrt(mass*stiffness) // point where the system comes to rest in the shortest period of time
let underDamping: CGFloat = damping * 0.5
let initialVelocity: CGVector = CGVector.zero
let springParameters: UISpringTimingParameters = UISpringTimingParameters(mass: mass, stiffness: stiffness, damping: underDamping, initialVelocity: initialVelocity)
let animationDelay = 3
let pulseEffect = UIViewPropertyAnimator(duration: 5, timingParameters: springParameters)
pulseEffect.addAnimations( {[weak self] in
btnView!.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
pulseEffect.isReversed = true
pulseEffect.startAnimation(afterDelay: TimeInterval(animationDelay))
iOS 10 introduces a new object called UIViewPropertyAnimator
.
You can do the same things you did with UIView.animation
but for achieving more complex animations, apple provides us CoreAnimation
framework.
Why we should use it?
Because UIViewPropertyAnimator
is a well-written and highly-extensible API. It covers a lot of the same functionality as the ‘old-style’ UIView
animations, but gives you fine-grained programmatic control over the animation. This means you can pause an in-progress animation, restart it at a later date if you wish and even dynamically modify the animatable properties (such as changing the animation’s end point to the top-right of the screen when it was previously the bottom-left).
We create an instance of it and create an animation as follows:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.alpha = 0.35
view.backgroundColor = .red
self.view.addSubview(view)
let newFrame = CGRect(x: 0, y: 0, width: 50, height: 50)
let viewFrameAnimator = UIViewPropertyAnimator(duration: 1.0,
curve: .linear,
animations: { view.frame = newFrame })
Now we can interacting with the animations using:
viewFrameAnimator.startAnimation..
viewFrameAnimator.stopAnimation..
viewFrameAnimator.finishAnimation(at:..
to continue the example above:
viewFrameAnimator.startAnimation()
Then, to return to alpha
equal to 1, if we need more animations options, we can also launch this UIViewPropertyAnimator
open class:
let viewAlphaAnimator = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0,
delay: 0.0,
options: [.curveLinear],
animations: { view.alpha = 1.0 })
let give us to add more animation options. Now you know more than this new object.
How to repeat it?
So let's start to explain how to repeat the animation, for example if we want to repeat the view.alpha = 1.0
for 3 times (you can see the little flash light for each repeation to output..) you should follow:
let viewAlphaAnimator = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0,
delay: 0.0,
options: [.curveLinear,.repeat],
animations: { UIView.setAnimationRepeatCount(3);view.alpha = 1.0 })
I hope this can help you.
For whatever reason they left out the ability to specify a repeat count with UIPropertyAnimator. You are left with he following options:
- Keep a counter and repeat the animation in the completion handler
and decrement the counter until the counter reaches 0
- Animate with an indefinite repeat but schedule a timer and halt the
animation (optionally add an animation to move to the final
position)
- Adjust your spring parameters so that there is less dampening and
more elasticity and you will get more oscillations.
- Use the old UIView.animate methods along with the old
UIView.setAnimationRepeatCount
- Use CASpringAnimation since thats what the Property animator is wrapping anyways, and it does have a repeat count.
Please use
pulseEffect.addCompletion({_ in
// Repeat the logic here
})
You can repeat the animation logic inside the addCompletion block
func animateView() {
btnView.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
let mass: CGFloat = 2.0 // weight of the object
let stiffness: CGFloat = 25.0 // elasticity
let damping: CGFloat = 2*sqrt(mass*stiffness) // point where the system comes to rest in the shortest period of time
let underDamping: CGFloat = damping * 0.5
let initialVelocity: CGVector = CGVector.zero
let springParameters: UISpringTimingParameters = UISpringTimingParameters(mass: mass, stiffness: stiffness, damping: underDamping, initialVelocity: initialVelocity)
let animationDelay = 3
let pulseEffect = UIViewPropertyAnimator(duration: 5, timingParameters: springParameters)
pulseEffect.addAnimations( {[weak self] in
self?.btnView!.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
pulseEffect.addCompletion({_ in
self.animateView()
})
pulseEffect.isReversed = true
pulseEffect.startAnimation(afterDelay: TimeInterval(animationDelay))
}
You can also add the logic to restrict the animation to specific number in the addCompletion block
'setAnimationRepeatCount' was deprecated in iOS 13.0. Options: [.autoreverse, .repeat] is not working too.
var animator: UIViewPropertyAnimator!
func start(_ reversed: Bool = false) {
animator = UIViewPropertyAnimator()
animator.addAnimations {
//
}
animator.addCompletion { _ in
start(!reversed)
}
animator.startAnimation()
}
func stop() {
animator.stopAnimation(true)
}
This code works:
func usePropertyAnimator(_ reversed: Bool = false) {
let circleAnimator = UIViewPropertyAnimator(
duration: 2, timingParameters: UICubicTimingParameters())
circleAnimator.addAnimations {
circle.center.y += (reversed ? -1 : 1) * 330
}
circleAnimator.addCompletion { _ in
usePropertyAnimator(!reversed)
}
circleAnimator.startAnimation()
}
usePropertyAnimator()
Please use this modified code
let btnView = sayWordBtn.viewWithTag(0)
btnView.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
let mass: CGFloat = 2.0 // weight of the object
let stiffness: CGFloat = 25.0 //elasticity
let damping: CGFloat = 2*sqrt(mass*stiffness) // point where the system comes to rest in the shortest period of time
let underDamping: CGFloat = damping * 0.5
let initialVelocity: CGVector = CGVector.zero
let springParameters: UISpringTimingParameters = UISpringTimingParameters(mass: mass, stiffness: stiffness, damping: underDamping, initialVelocity: initialVelocity)
let animationDelay = 3
let pulseEffect = UIViewPropertyAnimator(duration: 5, timingParameters: springParameters)
pulseEffect.addAnimations( {[weak self] in
UIView.setAnimationRepeatCount(3)
UIView.setAnimationRepeatAutoreverses(true)
btnView!.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
pulseEffect.isReversed = true
pulseEffect.startAnimation(afterDelay: TimeInterval(animationDelay))