I want to animate a text that starts by default from 0, to a variable.
For example, for x = 80, I want my text to display all the numbers between 0 and 80 very fast, until it hits 80. I found examples with progress indicators, but I cannot apply the methods to this.
Do you have any ideas for doing this?
Thanks, Diocrasis.
Here's a solution that leverages SwiftUI's built-in animation capabilities. As a result, it doesn't need to manage any timers or animation loops itself.
It's not limited to moving from 0 to a final number, either. Any change in the value performed in a withAnimation block will animate the change in value by moving through intermediate values.
import SwiftUI
struct AnimatedNumberTextView<Content>: View, Animatable where Content: View {
private var value: Double
@ViewBuilder private let content: (Int) -> Content
init(_ value: Int, content: @escaping (Int) -> Content) {
self.value = Double(value)
self.content = content
}
var animatableData: Double {
get { value }
set { value = newValue }
}
var body: some View {
content(Int(value))
}
}
struct ContentView: View {
@State private var number = 0
var body: some View {
HStack {
Text("Riches:")
AnimatedNumberTextView(number) { value in
Text("\(value) 😮")
.monospaced()
}.frame(minWidth: 140, alignment: .trailing)
}
.padding(.bottom)
.onAppear {
withAnimation(.easeInOut(duration: 2)) {
number = 87654321
}
}
Button("Randomise!") {
withAnimation(.easeInOut(duration: 1)) {
number = Int.random(in: 0...87654321)
}
}
}
}
(Inspired by this blog and this blog.)
Here I've created a little function called runCounter which takes a binding to the counter variable, a start value, the end value, and the speed. When called, it sets the bound variable to the start value, and then starts a Timer which runs every speed seconds and increments the counter until it reaches end at which point it invalidates the timer.
This standalone example shows two counters running at different speeds, both of which start when they first appear using .onAppear().
struct ContentView: View {
@State private var counter1 = 0
@State private var counter2 = 0
var body: some View {
VStack {
Text("\(self.counter1)")
.onAppear {
self.runCounter(counter: self.$counter1, start: 0, end: 80, speed: 0.05)
}
Text("\(self.counter2)")
.onAppear {
self.runCounter(counter: self.$counter2, start: 0, end: 10, speed: 0.5)
}
}
}
func runCounter(counter: Binding<Int>, start: Int, end: Int, speed: Double) {
counter.wrappedValue = start
Timer.scheduledTimer(withTimeInterval: speed, repeats: true) { timer in
counter.wrappedValue += 1
if counter.wrappedValue == end {
timer.invalidate()
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With