How can I repeat animation (using UIViewPropertyAn

2020-02-28 00:20发布

问题:

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))

回答1:

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.



回答2:

For whatever reason they left out the ability to specify a repeat count with UIPropertyAnimator. You are left with he following options:

  1. Keep a counter and repeat the animation in the completion handler and decrement the counter until the counter reaches 0
  2. Animate with an indefinite repeat but schedule a timer and halt the animation (optionally add an animation to move to the final position)
  3. Adjust your spring parameters so that there is less dampening and more elasticity and you will get more oscillations.
  4. Use the old UIView.animate methods along with the old UIView.setAnimationRepeatCount
  5. Use CASpringAnimation since thats what the Property animator is wrapping anyways, and it does have a repeat count.


回答3:

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



回答4:

'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)
}


回答5:

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()


回答6:

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))