I have an app built in SwiftUI that uses a custom slider to change a value. This value controls the color of a rectangle. When using the slider, it properly changes the value but the color of the rectangle does not update. It does work normally with a normal built-in slider.
Here is the code for the view
struct SliderTestView: View {
@State var redValue = 0.0
@State var greenValue = 0.5
@State var blueValue = 0.5
var body: some View {
VStack {
Text("Hello, World! The red value is: \(redValue)")
Rectangle()
.foregroundColor(Color(red: redValue, green: greenValue, blue: blueValue, opacity: 1.0))
.cornerRadius(10)
CSlider(value: $redValue)
}
.padding()
}
}
struct CSlider: View {
@Binding var value: Double
var body: some View {
CustomSlider(value: $value, range: (0, 100)) { modifiers in
ZStack {
LinearGradient(gradient: .init(colors: [Color.pink, Color.red]), startPoint: .leading, endPoint: .trailing)
ZStack {
Circle().fill(Color.white)
Circle().stroke(Color.black.opacity(0.2), lineWidth: 2)
Text(("\(Int(self.value))")).font(.system(size: 11))
}
.padding([.top, .bottom], 2)
.modifier(modifiers.knob)
}.cornerRadius(15)
}.frame(height: 30)
}
}
And here is the logic code for the custom slider.
struct CustomSlider<Component: View>: View {
@Binding var value: Double
var range: (Double, Double)
var knobWidth: CGFloat?
let viewBuilder: (CustomSliderComponents) -> Component
init(value: Binding<Double>, range: (Double, Double), knobWidth: CGFloat? = nil, _ viewBuilder: @escaping (CustomSliderComponents) -> Component
) {
_value = value
self.range = range
self.viewBuilder = viewBuilder
self.knobWidth = knobWidth
}
var body: some View {
return GeometryReader { geometry in
self.view(geometry: geometry) // function below
}
}
private func view(geometry: GeometryProxy) -> some View {
let frame = geometry.frame(in: .global)
let drag = DragGesture(minimumDistance: 0).onChanged({ drag in
self.onDragChange(drag, frame) }
)
let offsetX = self.getOffsetX(frame: frame)
let knobSize = CGSize(width: knobWidth ?? frame.height, height: frame.height)
let barLeftSize = CGSize(width: CGFloat(offsetX knobSize.width * 0.5), height: frame.height)
let barRightSize = CGSize(width: frame.width - barLeftSize.width, height: frame.height)
let modifiers = CustomSliderComponents(
barLeft: CustomSliderModifier(name: .barLeft, size: barLeftSize, offset: 0),
barRight: CustomSliderModifier(name: .barRight, size: barRightSize, offset: barLeftSize.width),
knob: CustomSliderModifier(name: .knob, size: knobSize, offset: offsetX))
return ZStack { viewBuilder(modifiers).gesture(drag) }
}
private func onDragChange(_ drag: DragGesture.Value,_ frame: CGRect) {
let width = (knob: Double(knobWidth ?? frame.size.height), view: Double(frame.size.width))
let xrange = (min: Double(0), max: Double(width.view - width.knob))
var value = Double(drag.startLocation.x drag.translation.width) // knob center x
value -= 0.5*width.knob // offset from center to leading edge of knob
value = value > xrange.max ? xrange.max : value // limit to leading edge
value = value < xrange.min ? xrange.min : value // limit to trailing edge
value = value.convert(fromRange: (xrange.min, xrange.max), toRange: range)
self.value = value
print("KNOB IS MOVING. The value is: \(self.value)")
}
private func getOffsetX(frame: CGRect) -> CGFloat {
let width = (knob: knobWidth ?? frame.size.height, view: frame.size.width)
let xrange: (Double, Double) = (0, Double(width.view - width.knob))
let result = self.value.convert(fromRange: range, toRange: xrange)
return CGFloat(result)
}
}
struct CustomSliderComponents {
let barLeft: CustomSliderModifier
let barRight: CustomSliderModifier
let knob: CustomSliderModifier
}
struct CustomSliderModifier: ViewModifier {
enum Name {
case barLeft
case barRight
case knob
}
let name: Name
let size: CGSize
let offset: CGFloat
func body(content: Content) -> some View {
content
.frame(width: size.width)
.position(x: size.width*0.5, y: size.height*0.5)
.offset(x: offset)
}
}
//MARK: - Double Ext.
extension Double {
func convert(fromRange: (Double, Double), toRange: (Double, Double)) -> Double {
// Example: if self = 1, fromRange = (0,2), toRange = (10,12) -> solution = 11
var value = self
value -= fromRange.0
value /= Double(fromRange.1 - fromRange.0)
value *= toRange.1 - toRange.0
value = toRange.0
return value
}
}
CodePudding user response:
Your problem is that the redValue
being set by your slider is a value in the range 0.0...100.0
, but you need a value in the range 0.0...1.0
when setting the color of the Rectangle
:
Rectangle()
.foregroundColor(Color(red: redValue/100, green: greenValue, blue: blueValue, opacity: 1.0))
.cornerRadius(10)