Changing duration for Slider animation doesn't make any difference

Viewed 118

I'm trying to animate Slider smoothly. My original problem was animating it for 1 second every second for a small value (building an audio player scrubber), but the animation was a split second instead of a full second.

To isolate a problem, I built a playground where I'm trying to animate Slider change from 0 to maxValue for maxValue number of seconds. However, whatever maxValue is, the animation happens in a fraction of a second.

Here is the code:

struct SliderTest: View {
    @State private var sliderValue = 0.0
    let maxValue = 30.0

    var body: some View {
        VStack(spacing: 10) {
            Slider(value: $sliderValue, in: 0...maxValue)
            Button("Animate Slider") {
                withAnimation(.linear(duration: maxValue-sliderValue)) {
                    sliderValue = maxValue
                }
            }
            Button("Reset to Random") {
                sliderValue = Double.random(in: 0..<maxValue)
            }
            Button("Reset") {
                sliderValue = 0
            }
        }
    }
}

Here you can get the code with a preview for Swift Playgrounds: https://gist.github.com/OgreSwamp/6e6423d6ef2d26425e3f993042ac208d

1 Answers

First of all, animation is not working here because when it comes into withAnimation it will assign sliderValue to maxValue immediately. That causes the reason above like you said.

For the problem, I will separate the slider just for change UI depends on sliderValue only. Then building sliderTimer for handle the change value of slider and everytime the timer is excuted it will automatically increase sliderValue by step.

The code will be like this

struct ContentView: View {
    @State private var sliderValue = 0.0
    private var maxValue = 30.0
    private var sliderTimeAnimation = 2.0
    private var step = 1.0
    @State private var sliderTimer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        VStack(spacing: 10) {
            Slider(
                value: $sliderValue,
                in: 0...maxValue
            ).onReceive(sliderTimer) {
                _ in
                sliderValue += step
                if (sliderValue > maxValue) {
                    stopTimer()
                }
            }
            .onAppear() {
                self.stopTimer()
            }
            Button("Animate Slider") {
                startTimer()
            }
            Button("Reset to Random") {
                stopTimer()
                sliderValue = Double.random(in: 0..<maxValue)
            }
            Button("Reset") {
                stopTimer()
                sliderValue = 0
            }
        }
    }
    
    func startTimer() {
        self.sliderTimer = Timer.publish(every: sliderTimeAnimation * step / maxValue, on: .main, in: .common).autoconnect()
    }
    
    func stopTimer() {
        self.sliderTimer.upstream.connect().cancel()
    }
}

In this case, I set sliderTimeAnimation is 2.0, step is 1.0 (if you need faster animation you can set step is 0.5 or lower) and the max value is 30.0 like your code.

The result is like this. The result

Related