I want to animate a number. The animation I want to achieve is going from 0 increasing all the way up to the current number (at high speed). In this project, the number is the number of steps a user has taken. Is there a way this can be achieved?
LazyVStack{
ForEach(steps, id: \.id) { step in
//Here is the number I want to be animated
Text("\(step.count)")
.font(.custom(customFont, size: 50))
Text("Steps")
.font(.custom(customFont, size: 25))
.multilineTextAlignment(.center)
}
}
I believe I have a function along the right lines, I just need to apply it! Here is the function:
func addNumberWithRollingAnimation() {
withAnimation {
// Decide on the number of animation tasks
let animationDuration = 1000 // milliseconds
let tasks = min(abs(self.enteredNumber), 100)
let taskDuration = (animationDuration / tasks)
// add the remainder of our entered num from the steps
total = self.enteredNumber % tasks
// For each task
(0..<tasks).forEach { task in
// create the period of time when we want to update the number
// I chose to run the animation over a second
let updateTimeInterval = DispatchTimeInterval.milliseconds(task * taskDuration)
let deadline = DispatchTime.now() updateTimeInterval
// tell dispatch queue to run task after the deadline
DispatchQueue.main.asyncAfter(deadline: deadline) {
// Add piece of the entire entered number to our total
self.total = Int(self.enteredNumber / tasks)
}
}
}
}
CodePudding user response:
Here is a utility function called Timer.animateNumber()
which takes a Binding<Int>
to animate, a Binding<Bool> busy
which indicates if the value is currently animating, and Int start
value, an Int end
value, and a Double duration
in seconds.
To use it, you need to define an @State private var number: Int
to animate, and @State private var busy: Bool
to keep track of the animation's state. This can also be used to terminate the animation early by just setting busy
to false
. Pass in your start
value, end
value, and duration
in seconds.
This demo shows two animated numbers. The first counts up from 1 to 10000 in 1 second. The second counts down from 20 to 0 in 20 seconds. The Stop All
button can be used to stop both animations.
extension Timer {
static func animateNumber(number: Binding<Int>, busy: Binding<Bool>, start: Int, end: Int, duration: Double = 1.0) {
busy.wrappedValue = true
let startTime = Date()
Timer.scheduledTimer(withTimeInterval: 1/120, repeats: true) { timer in
let now = Date()
let interval = now.timeIntervalSince(startTime)
if !busy.wrappedValue {
timer.invalidate()
}
if interval >= duration {
number.wrappedValue = end
timer.invalidate()
busy.wrappedValue = false
} else {
number.wrappedValue = start Int(Double(end - start)*(interval/duration))
}
}
}
}
struct ContentView: View {
@State private var number: Int = 0
@State private var number2: Int = 0
@State private var busy: Bool = false
@State private var busy2: Bool = false
var body: some View {
VStack(spacing: 20) {
Text(String(number))
Button("Go") {
if !busy {
Timer.animateNumber(number: $number, busy: $busy, start: 1, end: 10000, duration: 1)
}
}
Text(String(number2))
Button("Go") {
if !busy2 {
Timer.animateNumber(number: $number2, busy: $busy2, start: 20, end: 0, duration: 20)
}
}
Button("Stop All") {
busy = false
busy2 = false
}
}
}
}