SwiftUI - Animating count text from 0 to x

Viewed 1800

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.

2 Answers

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

You can use a Timer.Publisher to trigger incrementing of your counter at regular intervals.

To stop incrementing once you reach your desired count, whenever your Timer fires, you can check if count has reached end, if not, increment it, otherwise remove the subscription and hence stop incrementing.

class Counter: ObservableObject {
    @Published var count = 0
    let end: Int

    private var timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
    private var subscriptions = Set<AnyCancellable>()

    init(end: Int) {
        self.end = end
    }

    func start() {
        timer.sink { [weak self] _ in
            guard let self = self else { return }
            if self.count <=self.end {
                self.count += 1
            } else {
                self.subscriptions.removeAll()
            }
        }.store(in: &subscriptions)
    }
}

struct AnimatedText: View {
    @ObservedObject var counter: Counter

    var body: some View {
        Text("\(counter.count)")
            .onAppear() {
                self.counter.start()
            }
    }
}


struct AnimatedText_Previews: PreviewProvider {
    static var previews: some View {
        AnimatedText(counter: Counter(end: 80))
    }
}
Related