CompactSlider

repository·main·Indexed 19 days ago

https://github.com/buh/compactslider

A highly customizable, high-performance SwiftUI slider control designed to replace the built-in Slider. It supports single values, ranges, multiple values, 2D grids, and polar circular grids. Features include precision control, scroll wheel support for macOS, and a variety of styles including linear, scrollable, and prominent layouts. Compatible with Swift 5.9+, Xcode 16+, iOS 15+, macOS 12+, watchOS 8+, and visionOS 1+.

Tokens
3.5K
Snippets
11
Records
13
Agent score
66%

What's inside CompactSlider

  1. Overview of CompactSlider

    main
    CompactSlider is a SwiftUI-optimized control designed for selecting values from a bounded linear range. It serves as a high-performance replacement for the built-in SwiftUI Slider, specifically addressing performance issues during window resizing or animations on macOS. Its design is inspired by the slider implementation found in Apple's Photos app.
  2. Access CompactSliderState for advanced layouts

    main

    For complex UI requirements, you can subscribe to the slider's internal state using the state: parameter. By passing a binding to a CompactSliderState object, you can access properties like dragLocationX.lower to position other views (like custom labels) relative to the slider handle's position.

    @State private var value: Double = 0.5
    @State private var sliderState: CompactSliderState = .zero
    
    var body: some View {
        ZStack {
            CompactSlider(value: $value, state: $sliderState) {}
            
            Text("\(Int(100 * value))%")
                .offset(x: sliderState.dragLocationX.lower)
                .allowsHitTesting(false)
        }
    }
  3. Basic usage of CompactSlider

    main

    You can use CompactSlider to manage single values, ranges, or multiple values. Import CompactSlider to access the components.

    Single Value

    Use value: $binding for a single scalar value.

    Range and Steps

    Use in: range and step: value to constrain the slider and snap to increments.

    Range Selection

    Use from: $minBinding and to: $maxBinding to select a range between two values.

    Multiple Values

    Use values: $arrayBinding to manage an array of multiple markers.

    import CompactSlider
    
    // Single value
    @State private var volume: Double = 0.5
    CompactSlider(value: $volume)
        .frame(height: 44)
    
    // With range and steps
    @State private var brightness: Double = 50
    CompactSlider(value: $brightness, in: 0...100, step: 5)
        .frame(height: 44)
    
    // Range selection
    @State private var minPrice: Double = 20
    @State private var maxPrice: Double = 80
    CompactSlider(from: $minPrice, to: $maxPrice, in: 0...100)
        .frame(height: 44)
    
    // Multiple values
    @State private var markers: [Double] = [0.2, 0.5, 0.8]
    CompactSlider(values: $markers)
        .frame(height: 44)
  4. CompactSlider requirements

    main

    Ensure your environment meets the following requirements:

    • Swift: 5.9+
    • Xcode: 16+ (Use xcode15 branch for Xcode 15)
    • SwiftUI: 3+
    • macOS: 12+
    • iOS: 15+
    • watchOS: 8+
    • visionOS: 1+
  5. Install CompactSlider via Swift Package Manager

    main

    To add CompactSlider to your Xcode project, follow these steps:

    1. In Xcode, navigate to FileAdd Packages....
    2. Search for the following repository URL and click Add Package: https://github.com/buh/CompactSlider.git
    3. Select the target you wish to add the package to and click Add Package again.
    https://github.com/buh/CompactSlider.git
  6. Use CompactSlider for range values

    main

    To allow users to select a range of values, initialize the slider using the from: and to: parameters instead of a single value: binding. This provides two separate bindings for the lower and upper bounds of the selection.

    @State private var lowerValue: Double = 8
    @State private var upperValue: Double = 17
    
    var body: some View {
        HStack {
            Text("Working hours:")
            CompactSlider(from: $lowerValue, to: $upperValue, in: 6...20, step: 1) {
                Text("\(lowerValue) — \(upperValue)")
                Spacer()
            }
        }
    }
  7. Use CompactSlider for single values

    main

    A CompactSlider can be used to bind a single value to a linear track. By default, the range is 0.0...1.0. You can specify a custom range using the in: parameter and control the increment steps with the step: parameter.

    You can also use the alignment: parameter to determine where the slider indicates the selected value (e.g., .center).

    @State private var speed = 50.0
    
    var body: some View {
        CompactSlider(value: $speed, in: 0...100, step: 5) {
            Text("Speed")
            Spacer()
            Text("\(Int(speed))")
        }
    }
  8. Use SystemSlider for native styling

    main

    If you want a slider that uses the native system style instead of the custom CompactSlider design, use the SystemSlider component.

    import CompactSlider
    
    // Single value
    SystemSlider(value: $volume)
    
    // Range selection
    SystemSlider(from: $min, to: $max)
  9. Customize CompactSlider options and appearance

    main

    Interaction Options

    Use .compactSliderOptionsByAdding(...) to enable specific behaviors:

    • .tapToSlide: Set value on click.
    • .scrollWheel: macOS scroll wheel support.
    • .snapToSteps: Snap while dragging.
    • .precisionControl(): Drag perpendicular to the slider for fine control.

    Visual Customization

    • Colors: Use .accentColor(_:).
    • Scale: Use .compactSliderScale(visibility:lineLength:color:) to customize the tick marks.
    • Handle: Use .compactSliderHandleStyle(_:) to change the handle shape (e.g., .circle(visibility:radius:)).
    // Common Options
    CompactSlider(value: $value)
        .compactSliderOptionsByAdding(
            .tapToSlide,
            .scrollWheel,
            .snapToSteps,
            .precisionControl()
        )
    
    // Colors and styling
    CompactSlider(value: $value)
        .accentColor(.purple)
    
    // Custom scale
    CompactSlider(value: $value)
        .compactSliderScale(
            visibility: .always,
            lineLength: 8,
            color: .blue
        )
    
    // Custom handle
    CompactSlider(value: $value)
        .compactSliderHandleStyle(
            .circle(visibility: .always, radius: 20)
        )
  10. Implement a custom CompactSliderStyle

    main

    You can create custom slider appearances by implementing the CompactSliderStyle protocol. This follows the same pattern as SwiftUI's ButtonStyle. The makeBody(configuration:) method provides a Configuration object containing:

    • label: The content provided in the slider's closure.
    • isHovering: Boolean indicating if the user is hovering.
    • isDragging: Boolean indicating if the user is currently dragging the handle.

    Apply your custom style using the .compactSliderStyle() modifier.

    public struct CustomCompactSliderStyle: CompactSliderStyle {
        public func makeBody(configuration: Configuration) -> some View {
            configuration.label
                .foregroundColor(
                    configuration.isHovering || configuration.isDragging ? .orange : .black
                )
                .background(Color.orange.opacity(0.1))
                .accentColor(.orange)
                .clipShape(RoundedRectangle(cornerRadius: 12))
        }
    }
    
    // Usage
    CompactSlider(value: $value) {
        Text("Custom Style")
    }
    .compactSliderStyle(.custom)
  11. Configure CompactSlider types and layouts

    main

    Use the .compactSliderStyle(default:) modifier to change the layout and interaction model of the slider.

    Linear Sliders

    • Horizontal: Alignments include .leading, .center, and .trailing.
    • Vertical: Alignments include .top, .center, and .bottom.
    • Scrollable: Use .scrollable() for a fixed handle with a moving scale.

    Grid Sliders

    • Grid: Use .grid() for 2D point selection using a CGPoint binding.
    • Circular Grid: Use .circularGrid() for polar coordinate selection using a CompactSliderPolarPoint binding.
    // Horizontal alignments
    CompactSlider(value: $value)
        .compactSliderStyle(default: .horizontal(.leading))
    
    // Vertical alignments
    CompactSlider(value: $value)
        .compactSliderStyle(default: .vertical(.center))
        .frame(width: 44)
    
    // Scrollable (fixed handle, moving scale)
    CompactSlider(value: $value)
        .compactSliderStyle(default: .scrollable())
        .frame(height: 44)
    
    // Grid (2D point selection)
    @State private var point = CGPoint(x: 50, y: 50)
    CompactSlider(point: $point, in: .zero...CGPoint(x: 100, y: 100))
        .compactSliderStyle(default: .grid())
        .frame(width: 200, height: 200)
    
    // Circular grid (polar coordinates)
    @State private var polarPoint = CompactSliderPolarPoint(angle: .zero, normalizedRadius: 0.5)
    CompactSlider(polarPoint: $polarPoint)
        .compactSliderStyle(default: .circularGrid())
        .frame(width: 200, height: 200)
  12. Configure secondary slider appearance

    main

    You can customize the colors and shapes of secondary slider elements (progress view, handle, scale) using several modifiers:

    1. .compactSliderSecondaryColor(...):
      • Set a single color and opacity for all secondary elements.
      • Or, provide specific values for progressOpacity, handleOpacity, scaleOpacity, and secondaryScaleOpacity.
    2. .compactSliderSecondaryAppearance(...):
      • Allows setting ShapeStyle (like gradients) for the progressShapeStyle and focusedProgressShapeStyle.
      • Allows setting specific colors for the handleColor, scaleColor, and secondaryScaleColor.
    // Example: Using gradients and specific colors for secondary elements
    configuration.label
        .compactSliderSecondaryAppearance(
            progressShapeStyle: LinearGradient(
                colors: [.orange.opacity(0), .orange.opacity(0.5)],
                startPoint: .leading,
                endPoint: .trailing
            ),
            focusedProgressShapeStyle: LinearGradient(
                colors: [.yellow.opacity(0.2), .orange.opacity(0.7)],
                startPoint: .leading,
                endPoint: .trailing
            ),
            handleColor: .orange,
            scaleColor: .orange,
            secondaryScaleColor: .orange
        )