Skip to content

Instantly share code, notes, and snippets.

@prachigauriar
Last active April 11, 2024 16:26
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save prachigauriar/c508799bad359c3aa271ccc0865de231 to your computer and use it in GitHub Desktop.
Save prachigauriar/c508799bad359c3aa271ccc0865de231 to your computer and use it in GitHub Desktop.
SwiftUI Slider with Logarithmic Scale
extension Binding where Value == Double {
/// Returns a new version of the binding that scales the value logarithmically using the specified base. That is,
/// when getting the value, `log_b(value)` is returned; when setting it, the new value is `pow(base, newValue)`.
///
/// - Parameter base: The base to use.
func logarithmic(base: Double = 10) -> Binding<Double> {
Binding(
get: {
log10(self.wrappedValue) / log10(base)
},
set: { (newValue) in
self.wrappedValue = pow(base, newValue)
}
)
}
}
extension Slider where Label == EmptyView, ValueLabel == EmptyView {
/// Creates a new `Slider` with a base-10 logarithmic scale.
///
/// ## Example
///
/// @State private var frequency = 1.0
///
/// var body: some View {
/// Slider.withLog10Scale(value: $frequency, in: 1 ... 100)
/// }
///
/// - Parameters:
/// - value: A binding to the unscaled value.
/// - range: The unscaled range of values.
/// - onEditingChanged: Documentation forthcoming.
static func withLog10Scale(
value: Binding<Double>,
in range: ClosedRange<Double>,
onEditingChanged: @escaping (Bool) -> Void = { _ in }
) -> Slider {
return self.init(
value: value.logarithmic(),
in: log10(range.lowerBound) ... log10(range.upperBound),
onEditingChanged: onEditingChanged
)
}
}
@acamill
Copy link

acamill commented Apr 23, 2021

2021 Apri update

// credit: https://gist.github.com/prachigauriar/c508799bad359c3aa271ccc0865de231
extension Binding where Value == Double {
    /// Returns a new version of the binding that scales the value logarithmically using the specified base. That is,
    /// when getting the value, `log_b(value)` is returned; when setting it, the new value is `pow(base, newValue)`.
    ///
    /// - Parameter base: The base to use.
    func logarithmic(base: Double = 10) -> Binding<Double> {
        Binding.init(
            get: {
                log10(self.wrappedValue) / log10(base)
            },
            set: { (newValue) in
                self.wrappedValue = pow(base, newValue)
            })
    }
}

@prachigauriar
Copy link
Author

2021 Apri update

Thank you! I’ve updated the gist to incorporate these changes. Glad it was of use to you!

@darknoon
Copy link

When I tried using this on macOS, I encountered some glitches with the Slider. As far as I can tell, the issue is because certain values along the slider don't map back to the same floating-point value when run through log and pow().

Visually, the slider hitches back to a previous value and makes the slider no longer smooth. I tried to round the value before passing it to the slider, but that also causes issues with a mismatch between the two "sources of truth."

Best idea I could think of is making the log() representation the source of truth, as the zoomable scroll view can be more forgiving.

@prachigauriar
Copy link
Author

When I tried using this on macOS, I encountered some glitches with the Slider. As far as I can tell, the issue is because certain values along the slider don't map back to the same floating-point value when run through log and pow().

Visually, the slider hitches back to a previous value and makes the slider no longer smooth. I tried to round the value before passing it to the slider, but that also causes issues with a mismatch between the two "sources of truth."

Best idea I could think of is making the log() representation the source of truth, as the zoomable scroll view can be more forgiving.

@darknoon Can you post some sample code? I don’t see the glitches you’re describing. What range are you using?

@carlynorama
Copy link

Thanks for this, btw. I'm working on one I can use for different kinds of mappings and this got me started. It's still in progress.

https://gist.github.com/carlynorama/ad0a7449367b9a08e8c8525d5dcc2f8a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment