bjrnt
bjrnt

Reputation: 2822

Setting the value of a SwiftUI slider by tapping on it

How can I change the value of a SwiftUI Slider by tapping on it?

Upvotes: 3

Views: 1308

Answers (3)

Anton Kondrashov
Anton Kondrashov

Reputation: 227

Here is a fully correct implementation that takes into account that you can have different step values and lowerBound might be greater than 0.

The original soultion doesn't take the step into account. Also the frame is used to avoid the view shrinking.

struct TappableSlider: View {
    var value: Binding<Double>
    var range: ClosedRange<Double>
    var step: Double

    init(value: Binding<Double>, in range: ClosedRange<Double>, step: Double) {
        self.value = value
        self.range = range
        self.step = step
    }

    var body: some View {
        GeometryReader { geometry in
            Slider(value: value, in: range, step: step)
                .gesture(DragGesture(minimumDistance: 0).onChanged { gestureValue in
                    // Ensure the drag location is within the view's bounds and the step is not zero
                    guard gestureValue.location.x >= 0 && gestureValue.location.x <= geometry.size.width, step != 0 else { return }

                    // Calculate the percentage of the slider's width where the drag happened
                    let clampedPercent = max(0, min(gestureValue.location.x / geometry.size.width, 1))

                    // Scale this percentage to the slider's value range
                    let scaledRangeValue = range.lowerBound + (clampedPercent * (range.upperBound - range.lowerBound))

                    // Round this value to the nearest step
                    let roundedToStep = round(scaledRangeValue / step) * step

                    // Ensure the final value adheres to the slider's bounds
                    let clampedValue = max(range.lowerBound, min(roundedToStep, range.upperBound))

                    // Update the bound value
                    self.value.wrappedValue = clampedValue
                })
                .frame(
                    width: geometry.frame(in: .global).width,
                    height: geometry.frame(in: .global).height
                )
        }
    }
}

Upvotes: 0

Quoc Huynh
Quoc Huynh

Reputation: 1

Up date some thing here :

struct TappableSlider: View {
    var value: Binding<Double>
    var range: ClosedRange<Double>
    var step: Double

    var body: some View {
        GeometryReader { geometry in
            Slider(value: value, in: range, step: step)
                .gesture(DragGesture(minimumDistance: 0).onChanged { value in
                    let percent = min(max(0, (value.location.x / geometry.size.width)), 1)
                    let newValue = (percent * range.upperBound).round(nearest: step)
                    self.value.wrappedValue = newValue
                })
        }
    }
}

whith extension Double here :

extension Double {
    var clean: String {
        truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self)
    }

    func round(nearest: Double) -> Double {
        let n = 1 / nearest
        let numberToRound = self * n
        return numberToRound.rounded() / n
    }

    func floor(nearest: Double) -> Double {
        let intDiv = Double(Int(self / nearest))
        return intDiv * nearest
    }
}

Upvotes: 0

bjrnt
bjrnt

Reputation: 2822

Here's a minimal implementation I created using a GeometryReader that rounds to the closest value:

struct TappableSlider: View {
    var value: Binding<Float>
    var range: ClosedRange<Float>
    var step: Float

    var body: some View {
        GeometryReader { geometry in
            Slider(value: self.value, in: self.range, step: self.step)
                .gesture(DragGesture(minimumDistance: 0).onEnded { value in
                    let percent = min(max(0, Float(value.location.x / geometry.size.width * 1)), 1)
                    let newValue = self.range.lowerBound + round(percent * (self.range.upperBound - self.range.lowerBound))
                    self.value.wrappedValue = newValue
                })
        }
    }
}

which can be used just like a normal slider, e.g.

TappableSlider(value: $sliderValue, in: 1...7, step: 1.0)

Upvotes: 7

Related Questions