5

Using SwiftUI, I would like to create a slider with a min/max range of 5-20 where the user can slide and go up by 5. Slider out of the box looks very 'flat'/smooth. Is there a way I can add said tickmarks so that it looks like the slider is 'snapping' to the ticks?

If the slider is considered not to be the right way to do that effect, am open to other options.

0

2 Answers 2

7

The Slider has a bunch of initializer options, some of which include a 'step' parameter, which creates the snapping effect. It doesn't have visible tick marks along the edge, but you could create that manually alongside.

import SwiftUI

struct SliderView: View {
    
    @State var value: CGFloat = 10
    let min: CGFloat = 5
    let max: CGFloat = 20
    let step: CGFloat = 5
    
    var body: some View {
        VStack {
            Text(formated(value: value))
            
            Slider(
                value: $value,
                in: min...max,
                step: step,
                minimumValueLabel: Text(formated(value: min)),
                maximumValueLabel: Text(formated(value: max)),
                label: { })

            VStack(spacing: 0) {
                Slider(
                    value: $value,
                    in: min...max,
                    step: step)
                    .accentColor(.red)
                
                HStack(spacing: 0) {
                    let count: Int = Int((max / step))
                    ForEach(0..<count) { index in
                        VStack {
                            Text("I")
                            Text("\(index * Int(step) + Int(min))")
                        }
                        .offset(x:
                          index == 1 ? 5 :
                          index == 2 ? 4 :
                          0
                        )
                        if index != (count - 1) {
                            Spacer()
                        }
                    }
                }
                .padding(.horizontal, 8)
            }
            .padding()
        }
        .padding()
    }
    
    func formated(value: CGFloat) -> String {
        return String(format: "%.0f", value)
    }
    
}
Sign up to request clarification or add additional context in comments.

2 Comments

I notice some 'off-centered-ness with the tickmark and the circle for the slider. If there is something that could be done to make it align properly, it would be perfect!
I added an .offset() to the Vstack. It's not a scaleable solution but if your only going from 5-20, it works. You might have to adjust further if you change the shape/font/size/etc.
1

If you create the Slider using an initializer that doesn't add labels, then a simple way to add tick marks is to show them in an HStack in the background behind the Slider, with a Spacer between each one.

The only complication is that the slider button stops with its edge aligned with the edge of the slider groove, it does not go over the end of the groove. So it is necessary to add horizontal padding to the HStack, equivalent to half the width of the slider button.

If the tick marks are given a width of 2 points, it works quite well to use horizontal padding of 12:

Slider(value: $value, in: 5...20, step: 5)
    .frame(maxWidth: 300)
    .background {
        HStack {
            ForEach(0..<4) { i in
                Color.pink
                    .frame(width: 2)
                    .padding(.vertical, 5)
                if i < 3 {
                    Spacer()
                }
            }
        }
        .padding(.horizontal, 12)
    }

Screenshot


EDIT If you don't like hard-coding the width of the padding, you could use .onGeometryChange to measure the height of the slider. The height of the slider is determined by the thumb size + shadow, so this gives a reasonable approximation of the thumb diameter. You could make it more accurate by subtracting a few points for the shadow, but that would be using hard-coded values again. It works quite well even without the shadow correction:

@State private var thumbSize = CGFloat.zero
Slider(value: $value, in: 5...20, step: 5)
    .frame(maxWidth: 300)
    .background {
        HStack {
            // ... as above
        }
        .padding(.horizontal, thumbSize / 2)
    }
    .onGeometryChange(for: CGFloat.self) { proxy in
        proxy.size.height
    } action: { height in
        thumbSize = height
    }

If you need to label the slider range, you can always put the Slider inside an HStack and place your own min and max labels before and after the Slider. Alternatively, you can label each of the ticks. For ways of aligning labels with the tick marks, see the answer to How to space out varying width text in HStack so centre of the text is positioned correctly in SwiftUI.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.