Skip to content

Commit

Permalink
range slider minimum and maximum distance (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
ay42 committed Oct 12, 2022
1 parent 97046c9 commit 23ae552
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 54 deletions.
7 changes: 4 additions & 3 deletions README.md
Expand Up @@ -5,7 +5,8 @@ This package allows you to build highly customizable sliders and tracks for iOS,
- Build your own sliders and tracks using composition
- Highly customizable
- Horizontal and Vertical styles
- Range and XY values
- Range sliders with minimum/maximum value distance
- XY sliders
- Different sizes for lower and upper range thumbs

<center>
Expand Down Expand Up @@ -53,7 +54,7 @@ See the preview of each file to see an example
Use any SwiftUI view modifiers to create custom tracks and thumbs.

```swift
RangeSlider(range: $model.range2)
RangeSlider(range: $model.range2, distance: 0.1 ... 1.0)
.rangeSliderStyle(
HorizontalRangeSliderStyle(
track:
Expand Down Expand Up @@ -84,4 +85,4 @@ RangeSlider(range: $model.range2)
Feel free to contribute via fork/pull request to master branch. If you want to request a feature or report a bug please start a new issue.

## Coffee Contributions
If you find this project useful please consider becoming my GitHub sponsor.
If you find this project useful please consider becoming our GitHub sponsor.
24 changes: 24 additions & 0 deletions Sources/Sliders/Base/LinearRangeMath.swift
Expand Up @@ -7,3 +7,27 @@ import SwiftUI
let offsetUpperValue = distanceFrom(value: range.upperBound, availableDistance: overallLength, bounds: bounds, leadingOffset: upperStartOffset, trailingOffset: upperEndOffset)
return max(0, offsetUpperValue - offsetLowerValue)
}

@inlinable func rangeFrom(updatedLowerBound: CGFloat, upperBound: CGFloat, bounds: ClosedRange<CGFloat>, distance: ClosedRange<CGFloat>, forceAdjacent: Bool) -> ClosedRange<CGFloat> {
if forceAdjacent {
let finalLowerBound = min(updatedLowerBound, bounds.upperBound - distance.lowerBound)
let finalUpperBound = min(min(max(updatedLowerBound + distance.lowerBound, upperBound), updatedLowerBound + distance.upperBound), bounds.upperBound)
return finalLowerBound ... finalUpperBound
} else {
let finalLowerBound = min(updatedLowerBound, upperBound - distance.lowerBound)
let finalUpperBound = min(upperBound, updatedLowerBound + distance.upperBound)
return finalLowerBound ... finalUpperBound
}
}

@inlinable func rangeFrom(lowerBound: CGFloat, updatedUpperBound: CGFloat, bounds: ClosedRange<CGFloat>, distance: ClosedRange<CGFloat>, forceAdjacent: Bool) -> ClosedRange<CGFloat> {
if forceAdjacent {
let finalLowerBound = max(max(min(lowerBound, updatedUpperBound - distance.lowerBound), updatedUpperBound - distance.upperBound), bounds.lowerBound)
let finalUpperBound = max(updatedUpperBound, bounds.lowerBound + distance.lowerBound)
return finalLowerBound ... finalUpperBound
} else {
let finalLowerBound = max(lowerBound, updatedUpperBound - distance.upperBound)
let finalUpperBound = max(lowerBound + distance.lowerBound, updatedUpperBound)
return finalLowerBound ... finalUpperBound
}
}
2 changes: 1 addition & 1 deletion Sources/Sliders/PointSlider/PointSlider.swift
Expand Up @@ -38,7 +38,7 @@ extension PointSlider {
}

extension PointSlider {
public init<V>(x: Binding<V>, xBounds: ClosedRange<V> = 0...1, xStep: V.Stride = 1, y: Binding<V>, yBounds: ClosedRange<V> = 0...1, yStep: V.Stride = 1, onEditingChanged: @escaping (Bool) -> Void = { _ in }) where V : BinaryInteger, V.Stride : BinaryInteger {
public init<V>(x: Binding<V>, xBounds: ClosedRange<V> = 0...1, xStep: V.Stride = 1, y: Binding<V>, yBounds: ClosedRange<V> = 0...1, yStep: V.Stride = 1, onEditingChanged: @escaping (Bool) -> Void = { _ in }) where V : FixedWidthInteger, V.Stride : FixedWidthInteger {

self.init(
PointSliderStyleConfiguration(
Expand Down
38 changes: 26 additions & 12 deletions Sources/Sliders/RangeSlider/RangeSlider.swift
Expand Up @@ -20,16 +20,22 @@ extension RangeSlider {
}

extension RangeSlider {
public init<V>(range: Binding<ClosedRange<V>>, in bounds: ClosedRange<V> = 0.0...1.0, step: V.Stride = 0.001, onEditingChanged: @escaping (Bool) -> Void = { _ in }) where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint {

public init<V>(
range: Binding<ClosedRange<V>>,
in bounds: ClosedRange<V> = 0.0...1.0,
step: V.Stride = 0.001,
distance: ClosedRange<V> = 0.0 ... .infinity,
onEditingChanged: @escaping (Bool) -> Void = { _ in }
) where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint {
self.init(
RangeSliderStyleConfiguration(
range: Binding(
get: { CGFloat(range.wrappedValue.clamped(to: bounds).lowerBound)...CGFloat(range.wrappedValue.clamped(to: bounds).upperBound) },
set: { range.wrappedValue = V($0.lowerBound)...V($0.upperBound) }
get: { CGFloat(range.wrappedValue.clamped(to: bounds).lowerBound) ... CGFloat(range.wrappedValue.clamped(to: bounds).upperBound) },
set: { range.wrappedValue = V($0.lowerBound) ... V($0.upperBound) }
),
bounds: CGFloat(bounds.lowerBound)...CGFloat(bounds.upperBound),
bounds: CGFloat(bounds.lowerBound) ... CGFloat(bounds.upperBound),
step: CGFloat(step),
distance: CGFloat(distance.lowerBound) ... CGFloat(distance.upperBound),
onEditingChanged: onEditingChanged,
dragOffset: .constant(0)
)
Expand All @@ -38,16 +44,22 @@ extension RangeSlider {
}

extension RangeSlider {
public init<V>(range: Binding<ClosedRange<V>>, in bounds: ClosedRange<V> = 0...1, step: V.Stride = 1, onEditingChanged: @escaping (Bool) -> Void = { _ in }) where V : BinaryInteger, V.Stride : BinaryInteger {

public init<V>(
range: Binding<ClosedRange<V>>,
in bounds: ClosedRange<V> = 0...1,
step: V.Stride = 1,
distance: ClosedRange<V> = 0 ... .max,
onEditingChanged: @escaping (Bool) -> Void = { _ in }
) where V : FixedWidthInteger, V.Stride : FixedWidthInteger {
self.init(
RangeSliderStyleConfiguration(
range: Binding(
get: { CGFloat(range.wrappedValue.lowerBound)...CGFloat(range.wrappedValue.upperBound) },
set: { range.wrappedValue = V($0.lowerBound)...V($0.upperBound) }
get: { CGFloat(range.wrappedValue.lowerBound) ... CGFloat(range.wrappedValue.upperBound) },
set: { range.wrappedValue = V($0.lowerBound) ... V($0.upperBound) }
),
bounds: CGFloat(bounds.lowerBound)...CGFloat(bounds.upperBound),
bounds: CGFloat(bounds.lowerBound) ... CGFloat(bounds.upperBound),
step: CGFloat(step),
distance: CGFloat(distance.lowerBound) ... CGFloat(distance.upperBound),
onEditingChanged: onEditingChanged,
dragOffset: .constant(0)
)
Expand All @@ -59,7 +71,9 @@ struct RangeSlider_Previews: PreviewProvider {
static var previews: some View {
Group {
HorizontalRangeSlidersPreview()
.previewDisplayName("Horizontal Range Sliders")
VerticalRangeSlidersPreview()
.previewDisplayName("Vertical Range Sliders")
}
}
}
Expand All @@ -76,7 +90,7 @@ private struct HorizontalRangeSlidersPreview: View {
VStack {
RangeSlider(range: $range1)

RangeSlider(range: $range2)
RangeSlider(range: $range2, distance: 0.3 ... 1.0)
.rangeSliderStyle(
HorizontalRangeSliderStyle(
track:
Expand Down Expand Up @@ -180,7 +194,7 @@ private struct VerticalRangeSlidersPreview: View {
VerticalRangeSliderStyle()
)

RangeSlider(range: $range2)
RangeSlider(range: $range2, distance: 0.5 ... 0.7)
.rangeSliderStyle(
VerticalRangeSliderStyle(
track:
Expand Down
Expand Up @@ -4,6 +4,7 @@ public struct RangeSliderStyleConfiguration {
public let range: Binding<ClosedRange<CGFloat>>
public let bounds: ClosedRange<CGFloat>
public let step: CGFloat
public let distance: ClosedRange<CGFloat>
public let onEditingChanged: (Bool) -> Void
public var dragOffset: Binding<CGFloat?>

Expand Down
Expand Up @@ -13,8 +13,8 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu

private let options: RangeSliderOptions

let onSelectLower: () -> Void
let onSelectUpper: () -> Void
let onSelectLower: () -> Void
let onSelectUpper: () -> Void

public func makeBody(configuration: Self.Configuration) -> some View {
GeometryReader { geometry in
Expand Down Expand Up @@ -46,7 +46,7 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu
y: geometry.size.height / 2
)
.onTapGesture {
self.onSelectLower()
self.onSelectLower()
}
.gesture(
DragGesture()
Expand All @@ -73,14 +73,14 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu
leadingOffset: self.lowerThumbSize.width / 2,
trailingOffset: self.lowerThumbSize.width / 2
)

if self.options.contains(.forceAdjacentValue) {
let computedUpperBound = max(computedLowerBound, configuration.range.wrappedValue.upperBound)
configuration.range.wrappedValue = computedLowerBound...computedUpperBound
} else {
let computedLowerBound = min(computedLowerBound, configuration.range.wrappedValue.upperBound)
configuration.range.wrappedValue = computedLowerBound...configuration.range.wrappedValue.upperBound
}
configuration.range.wrappedValue = rangeFrom(
updatedLowerBound: computedLowerBound,
upperBound: configuration.range.wrappedValue.upperBound,
bounds: configuration.bounds,
distance: configuration.distance,
forceAdjacent: options.contains(.forceAdjacentValue)
)
}
.onEnded { _ in
configuration.dragOffset.wrappedValue = nil
Expand All @@ -104,7 +104,7 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu
y: geometry.size.height / 2
)
.onTapGesture {
self.onSelectUpper()
self.onSelectUpper()
}
.gesture(
DragGesture()
Expand All @@ -131,15 +131,14 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu
leadingOffset: self.lowerThumbSize.width + self.upperThumbSize.width / 2,
trailingOffset: self.upperThumbSize.width / 2
)

if self.options.contains(.forceAdjacentValue) {
let computedLowerBound = min(computedUpperBound, configuration.range.wrappedValue.lowerBound)
configuration.range.wrappedValue = computedLowerBound...computedUpperBound
} else {
let computedUpperBound = max(computedUpperBound, configuration.range.wrappedValue.lowerBound)
configuration.range.wrappedValue = configuration.range.wrappedValue.lowerBound...computedUpperBound
}


configuration.range.wrappedValue = rangeFrom(
lowerBound: configuration.range.wrappedValue.lowerBound,
updatedUpperBound: computedUpperBound,
bounds: configuration.bounds,
distance: configuration.distance,
forceAdjacent: options.contains(.forceAdjacentValue)
)
}
.onEnded { _ in
configuration.dragOffset.wrappedValue = nil
Expand Down
Expand Up @@ -65,14 +65,14 @@ public struct VerticalRangeSliderStyle<Track: View, LowerThumb: View, UpperThumb
leadingOffset: self.lowerThumbSize.height / 2,
trailingOffset: self.lowerThumbSize.height / 2
)

if self.options.contains(.forceAdjacentValue) {
let computedUpperBound = max(computedLowerBound, configuration.range.wrappedValue.upperBound)
configuration.range.wrappedValue = computedLowerBound...computedUpperBound
} else {
let computedLowerBound = min(computedLowerBound, configuration.range.wrappedValue.upperBound)
configuration.range.wrappedValue = computedLowerBound...configuration.range.wrappedValue.upperBound
}
configuration.range.wrappedValue = rangeFrom(
updatedLowerBound: computedLowerBound,
upperBound: configuration.range.wrappedValue.upperBound,
bounds: configuration.bounds,
distance: configuration.distance,
forceAdjacent: options.contains(.forceAdjacentValue)
)
}
.onEnded { _ in
configuration.dragOffset.wrappedValue = nil
Expand Down Expand Up @@ -118,14 +118,14 @@ public struct VerticalRangeSliderStyle<Track: View, LowerThumb: View, UpperThumb
leadingOffset: self.lowerThumbSize.height + self.upperThumbSize.height / 2,
trailingOffset: self.upperThumbSize.height / 2
)

if self.options.contains(.forceAdjacentValue) {
let computedLowerBound = min(computedUpperBound, configuration.range.wrappedValue.lowerBound)
configuration.range.wrappedValue = computedLowerBound...computedUpperBound
} else {
let computedUpperBound = max(computedUpperBound, configuration.range.wrappedValue.lowerBound)
configuration.range.wrappedValue = configuration.range.wrappedValue.lowerBound...computedUpperBound
}
configuration.range.wrappedValue = rangeFrom(
lowerBound: configuration.range.wrappedValue.lowerBound,
updatedUpperBound: computedUpperBound,
bounds: configuration.bounds,
distance: configuration.distance,
forceAdjacent: options.contains(.forceAdjacentValue)
)
}
.onEnded { _ in
configuration.dragOffset.wrappedValue = nil
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sliders/ValueSlider/ValueSlider.swift
Expand Up @@ -35,7 +35,7 @@ extension ValueSlider {
}

extension ValueSlider {
public init<V>(value: Binding<V>, in bounds: ClosedRange<V> = 0...1, step: V.Stride = 1, onEditingChanged: @escaping (Bool) -> Void = { _ in }) where V : BinaryInteger, V.Stride : BinaryInteger {
public init<V>(value: Binding<V>, in bounds: ClosedRange<V> = 0...1, step: V.Stride = 1, onEditingChanged: @escaping (Bool) -> Void = { _ in }) where V : FixedWidthInteger, V.Stride : FixedWidthInteger {
self.init(
ValueSliderStyleConfiguration(
value: Binding(get: { CGFloat(value.wrappedValue) }, set: { value.wrappedValue = V($0) }),
Expand Down

0 comments on commit 23ae552

Please sign in to comment.