ios – How to Mimic Default SwiftUI Slider Using Ideal Drag Gesture Logic?


When implementing slider gesture logic in SwiftUI, is there a way to do it that both preserves the subtle default/proper behaviors* as the default SwiftUI slider but…

Does not use/need a reference to the original bound value (the slider’s value when gesture began)?

I ask this because I have the following code/attempt,

// range of value can safely be assumed to be 0 to 1

struct CustomSlider_REF: View {

    @Binding var boundValue: Double
    
    let trackHeight:Double
    let circleSize:Double
    
    @State private var isDragging = false
    @State private var originalBoundValue: Double? // <--------

    var body: some View {

        GeometryReader { geo in
            
            let w = geo.size.width
            let h = geo.size.height
            
            let slideRange = w - circleSize
        
            let backgroundTrack = Rectangle()
                .foregroundColor(.gray)
                .frame(height: trackHeight)
            
            let selectedWidth = w * boundValue
            
            let coloredTrack = Rectangle()
                .foregroundColor(.blue)
                .frame(width: selectedWidth, height: trackHeight)
            
            // this is the left edge of the thumb
            let thumbOffset_X = boundValue * slideRange

            ZStack(alignment: .leading) {
                
                backgroundTrack
                
                coloredTrack

                // thumb
                Circle()
                    .foregroundColor(.white)
                    .frame(width: circleSize, height: circleSize)
                    .offset(x: thumbOffset_X)
                    .gesture(
                        DragGesture(minimumDistance: 0)
                            .onChanged { gestureValue in
                                if !isDragging {
                                    originalBoundValue = boundValue // <--------
                                    isDragging = true
                                }
                                
                                // how much has X changed since drag start?
                                // how much is that change in terms of the normalized range (0-1)?
                                let normalizedDelta = gestureValue.translation.width / slideRange
                                
                                // what is the result of adding that to current value?
                                let potentialNewValue = originalBoundValue! + normalizedDelta // <------ force unwrap
                                
                                // is that result out of bounds? if so, fix it
                                boundValue = min(max(potentialNewValue, 0), 1)
                                
                            }
                            .onEnded { _ in
                                if isDragging {
                                    isDragging = false
                                    // TODO:
                                    //self.onEditingChanged?(false)
                                } else {
                                    fatalError("greetings.  an unknown unknown has occurred.  look into this")
                                }
                                
                            }
                    )
                
            }
        }
        
        
    }
    


}

… but I don’t like that this reference value:

  • needs to be optional because it makes no sense to set a sentinel value as doing so would cause glitches if that sentinel value were ever needed

  • since it needs to be optional, then it needs to either be force unwrapped or we need to use a “guard let,” but it neither option seems good — force unwrapping could crash for “unknown unknown” reasons… and using guard let doesn’t make sense because it “must work.”

* – The main “subtle default behavior” I am referring to is the fact that in the default slider, if you start a drag slightly off target with the thumb, that “off-targetness” is preserved throughout the drag. If we didn’t care about this, then my question could be easily answered by simply directly updating the bound value using gesture.location.x.

Is the optional necessary and ok to use? If not, is there a way to do this without it?

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img