user8719510
user8719510

Reputation: 143

SwiftUI: How to move Slider Value with Thumb

I want to create slider in SwiftUI with Value label.

I have write below code.

struct SliderView: View {
    @State private var speed = 50.0
    var minValue: Double = 1
    var maxValue: Double = 100
    var body: some View {
        VStack {
            Text("\(speed,specifier: "%.f")")
                .foregroundColor(.blue)

            Slider(
                value: $speed,
                in: minValue...maxValue,
                step: 1
            ) {
                Text("Speed")
            } minimumValueLabel: {
                Text("\(minValue,specifier: "%.f")")
            } maximumValueLabel: {
                Text("\(maxValue,specifier: "%.f")")
            }

        }.padding()
    }
}

But I want to move value label with slider thumb.

I am not able to find any property to set .frame for Y value.

Question:

How to move Slider Value label with Thumb and move label value with Thumb change.

enter image description here

enter image description here

enter image description here

Upvotes: 3

Views: 2028

Answers (3)

Asperi
Asperi

Reputation: 257653

The problem here is that we do not have access to nob size and positioning (which are private) and refer only to range and current value, and this leads to some misalignment between slider's nob and hover value.

Anyway here is possible approach using alignment guide. Tuning to nob size I leave for you.

Tested with Xcode 13.4 / iOS 15.5

demo

struct SliderView: View {
    @State private var speed = 50.0

    var minValue: Double = 1
    var maxValue: Double = 100

    var body: some View {
        HStack {
            Text("\(minValue,specifier: "%.f")")
            Slider(value: $speed, in: minValue...maxValue, step: 1)
                .alignmentGuide(VerticalAlignment.center) { $0[VerticalAlignment.center]}
                .padding(.top)
                .overlay(GeometryReader { gp in
                    Text("\(speed,specifier: "%.f")").foregroundColor(.blue)
                        .alignmentGuide(HorizontalAlignment.leading) {
                            $0[HorizontalAlignment.leading] - (gp.size.width - $0.width) * speed / ( maxValue - minValue)
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)
                }, alignment: .top)
            Text("\(maxValue,specifier: "%.f")")
        }
        .padding()
    }
}

Test module on GitHub

Upvotes: 5

Kudos
Kudos

Reputation: 1490

I have tried @Asperi Answer and He did very well, just few updates required:

Problems:

  1. All was fine until your minValue is 1. But if minValue is more than 1 then the position of floating label is different.

Updated Code:

struct SliderView: View {
    @State var speed: Double
    var minValue: Double = 50
    var maxValue: Double = 100
    init(minVal: Double, maxVal: Double) {
        self.minValue = minVal
        self.maxValue = maxVal
        self.speed = minVal
    }
    
    var body: some View {
        HStack {
            Text("\(minValue,specifier: "%.f")")
            Slider(value: $speed, in: minValue...maxValue, step: 1)
                .alignmentGuide(VerticalAlignment.center) { $0[VerticalAlignment.center]}
                .padding(.top)
                .overlay(GeometryReader { gp in
                    VStack {
                    Text("\(speed,specifier: "%.f")").foregroundColor(.blue)
                        .alignmentGuide(HorizontalAlignment.leading) {
                            print("Hii \(speed) $0.width: \($0.width)")
                            return $0[HorizontalAlignment.leading] - (gp.size.width - $0.width) * (speed - minValue) / ( maxValue - minValue)
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                }, alignment: .top)
            Text("\(maxValue,specifier: "%.f")")
        }
        .padding()
    }
}

Usage:

 SliderView(minVal: 0, maxVal: 100)

Upvotes: 0

RelativeJoe
RelativeJoe

Reputation: 5084

First you need to read the width of your Slider, for that you're gonna need a GeometryReader. using the current value of the Slider, you can approximate the position of the thumb. Then you offset your Text:

struct SliderView: View {
    @State private var speed = 50.0
    var minValue: Double = 1
    var maxValue: Double = 100
    @State var width: CGFloat = 0
    var body: some View {
        VStack {
            Text("\(speed,specifier: "%.f")")
                .foregroundColor(.blue)
                .offset(x: width*speed/100)
            GeometryReader { proxy in
                Slider(
                    value: $speed,
                    in: minValue...maxValue,
                    step: 1
                ) {
                    Text("Speed")
                } minimumValueLabel: {
                    Text("\(minValue,specifier: "%.f")")
                } maximumValueLabel: {
                    Text("\(maxValue,specifier: "%.f")")
                }.onAppear {
                    self.width = proxy.frame(in: .global).width
                }
            }
        }.padding()
    }
}

Upvotes: 1

Related Questions