Reputation: 11597
I would like something that mimics the behaviour in iOS of the ValueAnimator
in Android. Under normal circumstances I would use a UIView
animation, but unfortunately the objects value im trying to interpolate over time isnt working with a normal animation.
In my particular case im using a Lottie
animation, and trying to change the progress of the animation via a UIView
animation, but instead of the animation changing the value of the progress over time, it just jumps to the final value. eg:
let lottieAnim = ...
lottieAnim.animationProgress = 0
UIView.animate(withDuration: 2) {
lottieAnim.animationProgress = 1
}
This example does not animate the lottie animation over time, but simply jumps to the end. I know lottie has methods to play the animation, but Im trying to use a custom animation curve to set the progress (its a progress animation that has no finite duration) which is why I need to use a UIView
animation to interpolate the value.
I need something that updates intermittently to mimic an animation, the first thing that comes to mind is Android ValueAnimator
class.
Upvotes: 2
Views: 1263
Reputation: 11597
I put together a class that will mimic a ValueAnimator
from android somewhat, and will allow you to specify your own animation curve if need be (default is just linear)
Simple usage
let valueAnimator = ValueAnimator(duration: 2) { value in
animationView.animationProgress = value
}
valueAnimator.start()
Advanced usage
let valueAnimator = ValueAnimator(from: 0, to: 100, duration: 60, animationCurveFunction: { time, duration in
return atan(time)*2/Double.pi
}, valueUpdater: { value in
animationView.animationProgress = value
})
valueAnimator.start()
You can cancel it at any point as well using:
valueAnimator.cancel()
Value Animator class in Swift 5
// swiftlint:disable:next private_over_fileprivate
fileprivate var defaultFunction: (TimeInterval, TimeInterval) -> (Double) = { time, duration in
return time / duration
}
class ValueAnimator {
let from: Double
let to: Double
var duration: TimeInterval = 0
var startTime: Date!
var displayLink: CADisplayLink?
var animationCurveFunction: (TimeInterval, TimeInterval) -> (Double)
var valueUpdater: (Double) -> Void
init (from: Double = 0, to: Double = 1, duration: TimeInterval, animationCurveFunction: @escaping (TimeInterval, TimeInterval) -> (Double) = defaultFunction, valueUpdater: @escaping (Double) -> Void) {
self.from = from
self.to = to
self.duration = duration
self.animationCurveFunction = animationCurveFunction
self.valueUpdater = valueUpdater
}
func start() {
displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink?.add(to: .current, forMode: .default)
}
@objc
private func update() {
if startTime == nil {
startTime = Date()
valueUpdater(from + (to - from) * animationCurveFunction(0, duration))
return
}
var timeElapsed = Date().timeIntervalSince(startTime)
var stop = false
if timeElapsed > duration {
timeElapsed = duration
stop = true
}
valueUpdater(from + (to - from) * animationCurveFunction(timeElapsed, duration))
if stop {
cancel()
}
}
func cancel() {
self.displayLink?.remove(from: .current, forMode: .default)
self.displayLink = nil
}
}
You could probably improve this to be more generic but this serves my purpose.
Upvotes: 6