I'm trying to create a slider in SwiftUI (in the same style as the native Slider, but ultimately with more functionality). In the below gif I've got my slider on top and SwiftUI's native slider on the bottom, both with the same range and value. When I drag the native slider around, my view updates but the circle strobes when I drag back and forward very quickly. Is there a way to prevent this strobing please? The gif's not that clear so I've provided the code to reproduce. Any pointers in the right direction appreciated!
Here's the code for my slider:
struct BarSlider: View {
@Binding var value: CGFloat
var range: ClosedRange<CGFloat>
var body: some View {
GeometryReader { geo in
ZStack {
HStack(spacing: 0) {
Rectangle()
.fill(Color.red)
.frame(width: $value.wrappedValue.map(from: range, to: 0...geo.size.width))
Rectangle()
.fill(Color.green)
}
HStack {
Circle()
.frame(width: 30, height: 30)
.offset(x: valueForKnob(geometry: geo))
Spacer()
}
}
.background(Color.blue)
}
}
private func valueForKnob(geometry: GeometryProxy) -> CGFloat {
$value.wrappedValue.map(from: range, to: 0...geometry.size.width) - (30 * valueAsPercent())
}
private func valueAsPercent() -> CGFloat {
let percent = ((value - range.lowerBound) / (range.upperBound - range.lowerBound) * 100) / 100
return percent.clamped(to: 0...1)
}
}
And here's both sliders:
struct ContentView: View {
@State private var amount = CGFloat(100)
var body: some View {
VStack {
BarSlider(value: $amount, range: 100...1000)
.frame(height: 100)
Slider(value: $amount, in: 100...1000)
}
}
}
And these extensions:
extension CGFloat {
func map(from: ClosedRange<CGFloat>, to: ClosedRange<CGFloat>) -> CGFloat {
let value = self.clamped(to: from)
let fromRange = from.upperBound - from.lowerBound
let toRange = to.upperBound - to.lowerBound
let result = (((value - from.lowerBound) / fromRange) * toRange) + to.lowerBound
return result
}
}
extension Comparable {
func clamped(to limits: ClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}
