Skip to content

How to use Slider in SwiftUI | SwiftUI Bootcamp #42

Sliders are the natural choice whenever users need to pick a value from a continuous or stepped range — star ratings, volume controls, brightness, font size. After this lesson you'll know how to configure a Slider with custom bounds and step size, add descriptive endpoint labels, react to edit events, and style the track with an accent color.

What You'll Learn

  • How to bind a Slider to a Double state variable with a custom range and step size
  • How to display the current value as formatted text that stays in sync with the slider position
  • How to add minimumValueLabel and maximumValueLabel to make the range self-documenting
  • How to use onEditingChanged to react when the user starts or stops dragging the thumb

Mental Model

Picture a Slider as a physical fader on an audio mixing board. The fader track is the range (e.g., 1 to 5), the knob is the current value, and the notches on the track are the step positions. When you define step: 1.0 the knob snaps to whole numbers just like a detented fader. The labels at each end of the fader — "1" and "5" — are the minimumValueLabel and maximumValueLabel. And onEditingChanged is like a sensor under the fader that fires when your hand first touches it and again when you let go.

The binding $sliderValue is the cable connecting the fader position to whatever is listening downstream — in this case a Text view showing the rating. Move the fader, the text updates. Everything is synchronised without any extra wiring.

Detailed Explanation

Slider in SwiftUI is a generic control typed to BinaryFloatingPoint — most commonly Double. The simplest form needs only a binding: Slider(value: $sliderValue). This gives a 0.0 to 1.0 range with continuous (no-step) movement.

Adding in: 1...5 restricts the track to a specific range. Adding step: 1.0 makes the thumb snap to integer positions, which is perfect for a 1–5 star rating. The step value must divide evenly into the range or you can end up with an unreachable maximum.

onEditingChanged is a closure that receives a Booltrue when the user begins dragging, false when they release. This is useful for showing a live preview only while the user is actively adjusting (e.g., temporarily hiding other UI, triggering a network request only after the user has finished, or as shown here, changing a color to provide visual feedback).

minimumValueLabel and maximumValueLabel accept any View, so you can use styled text, SF Symbols, or images as end-cap labels. These are read by VoiceOver to describe the range to users with visual impairments. The label parameter is not shown visually in most styles but is used for accessibility.

Code Structure

SliderBootcamp.swift builds a minimal rating selector. A Text displays the formatted integer rating, a fully configured Slider lets the user adjust it from 1 to 5 in whole-number steps, and the text color changes to green once the user touches the slider thumb. The commented-out lines show progressively simpler slider configurations for quick experimentation.

Complete Code

SliderBootcamp.swift

swift
import SwiftUI

struct SliderBootcamp: View {
    
    @State var sliderValue: Double = 3 // starts mid-range so the slider has room to move in both directions
    @State var color: Color = .red     // changes to green when the user begins editing, as visual feedback
    var body: some View {
        VStack {
            Text("Rating:")
            Text(
                String(format: "%.0f", sliderValue) // "%.0f" formats the Double as an integer string, e.g. "3" not "3.0"
                //"\(sliderValue)"  // would show "3.0" — less polished for a rating display
            )
            .foregroundColor(color)
            //Slider(value: $sliderValue)                            // simplest form: range 0.0–1.0, continuous
            //Slider(value: $sliderValue, in: 1...5)                 // constrained range, still continuous
            //Slider(value: $sliderValue, in: 1...5, step: 1.0)      // snaps to 1, 2, 3, 4, 5
            Slider(
                value: $sliderValue,
                in: 1...5,
                step: 1.0,
                onEditingChanged: { (_) in
                    color = .green  // fires with true when drag starts, false when drag ends; we just set green either way
                },
                minimumValueLabel:
                    Text("1")
                    .font(.largeTitle)
                    .foregroundColor(.orange)
                ,
                maximumValueLabel: Text("5"), // right end-cap label; adopts the VStack's default styling
                label: {
                    Text("Title") // used for accessibility, not displayed visually in .automatic style
                })
                .accentColor(.red) // colours the filled portion of the track to the left of the thumb
        }
    }
}

struct SliderBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        SliderBootcamp()
    }
}

Code Walkthrough

  1. @State var sliderValue: Double = 3 — The source of truth for the rating. Double is required by Slider's generic constraint. Starting at 3 places the thumb in the middle of the 1–5 range, which gives a better first impression than seeing the thumb at either extreme.

  2. @State var color: Color = .red — Tracks the text color independently of the slider value. This is a classic pattern for showing transient interaction state — the color is "live feedback" that something is happening, separate from the durable rated value.

  3. String(format: "%.0f", sliderValue) — Formats the Double to zero decimal places. Without this, the text would show "3.0" or even "3.0000000001" due to floating-point imprecision. "%.0f" rounds to the nearest integer for clean display.

  4. in: 1...5, step: 1.0 — Constrains the range and enables snapping. The step must divide the range evenly: (5 - 1) / 1.0 = 4 steps, covering values 1, 2, 3, 4, 5 — which is correct. An uneven step (e.g., step: 1.5) would skip the maximum value.

  5. onEditingChanged: { (_) in color = .green } — The closure receives true when the thumb starts moving and false when it stops. Here the parameter is discarded with _ because we treat both events the same way (set color to green). In real apps you might use the boolean to defer an API call until false (edit ended) to avoid spamming the server on every drag frame.

  6. minimumValueLabel and maximumValueLabel — These parameters accept any View. The minimum label is styled with .font(.largeTitle) and orange color to demonstrate that each label can have independent styling. Both labels are read by VoiceOver as part of the slider's accessibility description.

  7. .accentColor(.red) — Colors the filled track segment (from the minimum up to the current thumb position). Note that .tint() is preferred on iOS 15+; .accentColor() still works but is soft-deprecated.

Common Mistakes

Mistake: Using step values that don't divide the range evenly, causing the maximum to be unreachable
For example, in: 1...5, step: 1.5 produces positions 1.0, 2.5, 4.0 — the maximum of 5 is never reachable. Always verify that (max - min) is divisible by step. For a 1–5 whole-number rating, step: 1.0 is the correct choice.

Mistake: Displaying sliderValue directly as a string without formatting, producing ugly floating-point output
"\(sliderValue)" on a Double will produce "3.0" or sometimes a very long decimal. Always use String(format: "%.0f", sliderValue) (or Int(sliderValue)) when you want to show a whole number to the user.

Mistake: Using onEditingChanged to fire a network request on every frame while dragging
onEditingChanged fires twice per interaction — once at the start (true) and once at the end (false). However, if you bind a TextField or call an API directly from onChange(of: sliderValue) it fires on every frame of a drag, which can flood a server. Always debounce or use onEditingChanged's false state (editing ended) as the trigger for expensive operations.

Key Takeaways

  • Constrain Slider with in: and step: to create snapping, bounded controls that prevent invalid input.
  • Use String(format: "%.0f", sliderValue) to display a Double slider value as a clean integer — never interpolate a Double directly into user-facing text.
  • onEditingChanged fires at the start and end of a drag interaction, making it the right place to defer expensive work (API calls, heavy computation) until the user has finished adjusting.

Last updated: June 27, 2026

Released under the MIT License.