Search code examples
iosswiftuislider

UISlider get Value while Anmation runs


I animate a UISlider with this function:

func animateSlider(){
    slider.value = Float(min)
    UIView.animate(withDuration: 2.0, animations: {
        self.slider.setValue(Float(self.max), animated:true)
    })

When I tap the screen I want to get the current value but it only returns the maximum value. What can I do to solve that problem?


Solution

  • When you schedule an animation Core Animation sets the model layer's properties immediately, and the creates a presentation layer which you may inspect. This presentation layer is what you actually see on screen, and is valid to query its properties such as its frame, bounds, position, etc.

    You were asking for the model layer's value which is set immediately in your animation block, when instead what you want is what is displayed through the presentation layer.

    You can get the currently displayed presentation value from the label when there is an in-progress animation by using this extension on UISlider:

    extension UISlider {
        var currentPresentationValue: Float {
            guard let presentation = layer.presentation(),
                let thumbSublayer = presentation.sublayers?.max(by: {
                    $0.frame.height < $1.frame.height
                })
                else { return self.value }
    
            let bounds = self.bounds
            let trackRect = self.trackRect(forBounds: bounds)
            let minRect = self.thumbRect(forBounds: bounds, trackRect: trackRect, value: 0)
            let maxRect = self.thumbRect(forBounds: bounds, trackRect: trackRect, value: 1)
            let value = (thumbSublayer.frame.minX - minRect.minX) / (maxRect.minX - minRect.minX)
            return Float(value)
        }
    }
    

    And its usage:

    func animateSlider() {
        slider.value = 0
        DispatchQueue.main.async {
            UIView.animate(withDuration: 2, delay: 0, options: .curveLinear, animations: {
                self.slider.setValue(1, animated: true)
            }, completion: nil)
        }
    
        // quarter done:
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            print(self.slider.currentPresentationValue)
            // prints 0.272757, expected 0.25 with some tolerance
        }
    
        // half done:
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            print(self.slider.currentPresentationValue)
            // prints 0.547876, expected 0.5 with some tolerance
        }
    
        // three quarters done:
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
            print(self.slider.currentPresentationValue)
            // prints 0.769981, expected 0.75 with some tolerance
        }
    }