Search code examples
swiftswiftuislidervolume

How to implement a slider whose minimum track color is begin from center(value=0) not left in SwiftUI


I want to custom Slider in SwiftUI. Just something like this.

enter image description here

I tried Slider with GeometryReader but it isn't working.

//MARK: Left - Right Balance
GeometryReader { geo in
    VStack {
        Text("\(String(format: "%.2f", balanceVolume))")
        HStack {
            Text("L")
            Slider(value: $balanceVolume, in: minValue...maxValue, step: 0.1) {editing in
                print("editing", editing)
                isEditing = editing
                if !editing {
                    player.pan = Float(balanceVolume)
                }
             }
             .tint(.none)
             .accentColor(.gray)
             Text("R")
        }
     }
     .padding(20)
}

Thank you you all.


Solution

  • I have created a simple custom slider, I hope it helps

    Output: enter image description here

    Use:

    struct slider: View {
        @State var sliderPosition: Float = 50
        var body: some View {
            SliderView(value: $sliderPosition, bounds: 1...100).padding(.all)
            
        }
    }
    

    Code:

    struct SliderView: View {
        let currentValue: Binding<Float>
        let sliderBounds: ClosedRange<Int>
        
        public init(value: Binding<Float>, bounds: ClosedRange<Int>) {
            self.currentValue = value
            self.sliderBounds = bounds
        }
        
        var body: some View {
            GeometryReader { geomentry in
                sliderView(sliderSize: geomentry.size)
            }
        }
        
        
        @ViewBuilder private func sliderView(sliderSize: CGSize) -> some View {
            let sliderViewYCenter = sliderSize.height / 2
            let sliderViewXCenter = sliderSize.width / 2
            ZStack {
                RoundedRectangle(cornerRadius: 2)
                    .fill(Color.gray)
                    .frame(height: 3)
                ZStack {
                    let sliderBoundDifference = sliderBounds.count
                    let stepWidthInPixel = CGFloat(sliderSize.width) / CGFloat(sliderBoundDifference)
                    
                    
                    let thumbLocation = CGFloat(currentValue.wrappedValue) * stepWidthInPixel
                    
                    // Path between starting point to thumb
                    lineBetweenThumbs(from: .init(x: sliderViewXCenter, y: sliderViewYCenter), to: .init(x: thumbLocation, y: sliderViewYCenter))
                    
                    // Thumb Handle
                    let thumbPoint = CGPoint(x: thumbLocation, y: sliderViewYCenter)
                    thumbView(position: thumbPoint, value: Float(currentValue.wrappedValue))
                        .highPriorityGesture(DragGesture().onChanged { dragValue in
                            
                            let dragLocation = dragValue.location
                            let xThumbOffset = min(dragLocation.x, sliderSize.width)
                            
                            let newValue = Float(sliderBounds.lowerBound / sliderBounds.upperBound) + Float(xThumbOffset / stepWidthInPixel)
                            if newValue > Float(sliderBounds.lowerBound) && newValue < Float(sliderBounds.upperBound + 1) {
                                currentValue.wrappedValue = newValue
                                
                            }
                        })
                    
                    
                }
            }
        }
        
        @ViewBuilder func lineBetweenThumbs(from: CGPoint, to: CGPoint) -> some View {
            Path { path in
                path.move(to: from)
                path.addLine(to: to)
            }.stroke(Color.blue, lineWidth: 4)
        }
        
        @ViewBuilder func thumbView(position: CGPoint, value: Float) -> some View {
            ZStack {
                Text(String(round(value)))
                    .font(.headline)
                    .offset(y: -20)
                Circle()
                    .frame(width: 24, height: 24)
                    .foregroundColor(.accentColor)
                    .shadow(color: Color.black.opacity(0.16), radius: 8, x: 0, y: 2)
                    .contentShape(Rectangle())
            }
            .position(x: position.x, y: position.y)
        }
    }