How can I change the value of a SwiftUI Slider by tapping on it?
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)
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
)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With