Reputation: 2822
How can I change the value of a SwiftUI Slider
by tapping on it?
Upvotes: 3
Views: 1308
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
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
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