Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - Animating count text from 0 to x

Tags:

ios

swift

swiftui

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

like image 120
Graham Lea Avatar answered Jan 24 '26 19:01

Graham Lea


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()
            }
        }
    }
}
like image 40
vacawama Avatar answered Jan 24 '26 20:01

vacawama



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!