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?