Skip to content

Commit 23ae552

Browse files
authored
range slider minimum and maximum distance (#58)
1 parent 97046c9 commit 23ae552

File tree

9 files changed

+238
-54
lines changed

9 files changed

+238
-54
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ This package allows you to build highly customizable sliders and tracks for iOS,
55
- Build your own sliders and tracks using composition
66
- Highly customizable
77
- Horizontal and Vertical styles
8-
- Range and XY values
8+
- Range sliders with minimum/maximum value distance
9+
- XY sliders
910
- Different sizes for lower and upper range thumbs
1011

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

5556
```swift
56-
RangeSlider(range: $model.range2)
57+
RangeSlider(range: $model.range2, distance: 0.1 ... 1.0)
5758
.rangeSliderStyle(
5859
HorizontalRangeSliderStyle(
5960
track:
@@ -84,4 +85,4 @@ RangeSlider(range: $model.range2)
8485
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.
8586

8687
## Coffee Contributions
87-
If you find this project useful please consider becoming my GitHub sponsor.
88+
If you find this project useful please consider becoming our GitHub sponsor.

Sources/Sliders/Base/LinearRangeMath.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,27 @@ import SwiftUI
77
let offsetUpperValue = distanceFrom(value: range.upperBound, availableDistance: overallLength, bounds: bounds, leadingOffset: upperStartOffset, trailingOffset: upperEndOffset)
88
return max(0, offsetUpperValue - offsetLowerValue)
99
}
10+
11+
@inlinable func rangeFrom(updatedLowerBound: CGFloat, upperBound: CGFloat, bounds: ClosedRange<CGFloat>, distance: ClosedRange<CGFloat>, forceAdjacent: Bool) -> ClosedRange<CGFloat> {
12+
if forceAdjacent {
13+
let finalLowerBound = min(updatedLowerBound, bounds.upperBound - distance.lowerBound)
14+
let finalUpperBound = min(min(max(updatedLowerBound + distance.lowerBound, upperBound), updatedLowerBound + distance.upperBound), bounds.upperBound)
15+
return finalLowerBound ... finalUpperBound
16+
} else {
17+
let finalLowerBound = min(updatedLowerBound, upperBound - distance.lowerBound)
18+
let finalUpperBound = min(upperBound, updatedLowerBound + distance.upperBound)
19+
return finalLowerBound ... finalUpperBound
20+
}
21+
}
22+
23+
@inlinable func rangeFrom(lowerBound: CGFloat, updatedUpperBound: CGFloat, bounds: ClosedRange<CGFloat>, distance: ClosedRange<CGFloat>, forceAdjacent: Bool) -> ClosedRange<CGFloat> {
24+
if forceAdjacent {
25+
let finalLowerBound = max(max(min(lowerBound, updatedUpperBound - distance.lowerBound), updatedUpperBound - distance.upperBound), bounds.lowerBound)
26+
let finalUpperBound = max(updatedUpperBound, bounds.lowerBound + distance.lowerBound)
27+
return finalLowerBound ... finalUpperBound
28+
} else {
29+
let finalLowerBound = max(lowerBound, updatedUpperBound - distance.upperBound)
30+
let finalUpperBound = max(lowerBound + distance.lowerBound, updatedUpperBound)
31+
return finalLowerBound ... finalUpperBound
32+
}
33+
}

Sources/Sliders/PointSlider/PointSlider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ extension PointSlider {
3838
}
3939

4040
extension PointSlider {
41-
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 {
41+
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 {
4242

4343
self.init(
4444
PointSliderStyleConfiguration(

Sources/Sliders/RangeSlider/RangeSlider.swift

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,22 @@ extension RangeSlider {
2020
}
2121

2222
extension RangeSlider {
23-
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 {
24-
23+
public init<V>(
24+
range: Binding<ClosedRange<V>>,
25+
in bounds: ClosedRange<V> = 0.0...1.0,
26+
step: V.Stride = 0.001,
27+
distance: ClosedRange<V> = 0.0 ... .infinity,
28+
onEditingChanged: @escaping (Bool) -> Void = { _ in }
29+
) where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint {
2530
self.init(
2631
RangeSliderStyleConfiguration(
2732
range: Binding(
28-
get: { CGFloat(range.wrappedValue.clamped(to: bounds).lowerBound)...CGFloat(range.wrappedValue.clamped(to: bounds).upperBound) },
29-
set: { range.wrappedValue = V($0.lowerBound)...V($0.upperBound) }
33+
get: { CGFloat(range.wrappedValue.clamped(to: bounds).lowerBound) ... CGFloat(range.wrappedValue.clamped(to: bounds).upperBound) },
34+
set: { range.wrappedValue = V($0.lowerBound) ... V($0.upperBound) }
3035
),
31-
bounds: CGFloat(bounds.lowerBound)...CGFloat(bounds.upperBound),
36+
bounds: CGFloat(bounds.lowerBound) ... CGFloat(bounds.upperBound),
3237
step: CGFloat(step),
38+
distance: CGFloat(distance.lowerBound) ... CGFloat(distance.upperBound),
3339
onEditingChanged: onEditingChanged,
3440
dragOffset: .constant(0)
3541
)
@@ -38,16 +44,22 @@ extension RangeSlider {
3844
}
3945

4046
extension RangeSlider {
41-
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 {
42-
47+
public init<V>(
48+
range: Binding<ClosedRange<V>>,
49+
in bounds: ClosedRange<V> = 0...1,
50+
step: V.Stride = 1,
51+
distance: ClosedRange<V> = 0 ... .max,
52+
onEditingChanged: @escaping (Bool) -> Void = { _ in }
53+
) where V : FixedWidthInteger, V.Stride : FixedWidthInteger {
4354
self.init(
4455
RangeSliderStyleConfiguration(
4556
range: Binding(
46-
get: { CGFloat(range.wrappedValue.lowerBound)...CGFloat(range.wrappedValue.upperBound) },
47-
set: { range.wrappedValue = V($0.lowerBound)...V($0.upperBound) }
57+
get: { CGFloat(range.wrappedValue.lowerBound) ... CGFloat(range.wrappedValue.upperBound) },
58+
set: { range.wrappedValue = V($0.lowerBound) ... V($0.upperBound) }
4859
),
49-
bounds: CGFloat(bounds.lowerBound)...CGFloat(bounds.upperBound),
60+
bounds: CGFloat(bounds.lowerBound) ... CGFloat(bounds.upperBound),
5061
step: CGFloat(step),
62+
distance: CGFloat(distance.lowerBound) ... CGFloat(distance.upperBound),
5163
onEditingChanged: onEditingChanged,
5264
dragOffset: .constant(0)
5365
)
@@ -59,7 +71,9 @@ struct RangeSlider_Previews: PreviewProvider {
5971
static var previews: some View {
6072
Group {
6173
HorizontalRangeSlidersPreview()
74+
.previewDisplayName("Horizontal Range Sliders")
6275
VerticalRangeSlidersPreview()
76+
.previewDisplayName("Vertical Range Sliders")
6377
}
6478
}
6579
}
@@ -76,7 +90,7 @@ private struct HorizontalRangeSlidersPreview: View {
7690
VStack {
7791
RangeSlider(range: $range1)
7892

79-
RangeSlider(range: $range2)
93+
RangeSlider(range: $range2, distance: 0.3 ... 1.0)
8094
.rangeSliderStyle(
8195
HorizontalRangeSliderStyle(
8296
track:
@@ -180,7 +194,7 @@ private struct VerticalRangeSlidersPreview: View {
180194
VerticalRangeSliderStyle()
181195
)
182196

183-
RangeSlider(range: $range2)
197+
RangeSlider(range: $range2, distance: 0.5 ... 0.7)
184198
.rangeSliderStyle(
185199
VerticalRangeSliderStyle(
186200
track:

Sources/Sliders/RangeSlider/Style/RangeSliderStyleConfiguration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public struct RangeSliderStyleConfiguration {
44
public let range: Binding<ClosedRange<CGFloat>>
55
public let bounds: ClosedRange<CGFloat>
66
public let step: CGFloat
7+
public let distance: ClosedRange<CGFloat>
78
public let onEditingChanged: (Bool) -> Void
89
public var dragOffset: Binding<CGFloat?>
910

Sources/Sliders/RangeSlider/Styles/Horizontal/HorizontalRangeSliderStyle.swift

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu
1313

1414
private let options: RangeSliderOptions
1515

16-
let onSelectLower: () -> Void
17-
let onSelectUpper: () -> Void
16+
let onSelectLower: () -> Void
17+
let onSelectUpper: () -> Void
1818

1919
public func makeBody(configuration: Self.Configuration) -> some View {
2020
GeometryReader { geometry in
@@ -46,7 +46,7 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu
4646
y: geometry.size.height / 2
4747
)
4848
.onTapGesture {
49-
self.onSelectLower()
49+
self.onSelectLower()
5050
}
5151
.gesture(
5252
DragGesture()
@@ -73,14 +73,14 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu
7373
leadingOffset: self.lowerThumbSize.width / 2,
7474
trailingOffset: self.lowerThumbSize.width / 2
7575
)
76-
77-
if self.options.contains(.forceAdjacentValue) {
78-
let computedUpperBound = max(computedLowerBound, configuration.range.wrappedValue.upperBound)
79-
configuration.range.wrappedValue = computedLowerBound...computedUpperBound
80-
} else {
81-
let computedLowerBound = min(computedLowerBound, configuration.range.wrappedValue.upperBound)
82-
configuration.range.wrappedValue = computedLowerBound...configuration.range.wrappedValue.upperBound
83-
}
76+
77+
configuration.range.wrappedValue = rangeFrom(
78+
updatedLowerBound: computedLowerBound,
79+
upperBound: configuration.range.wrappedValue.upperBound,
80+
bounds: configuration.bounds,
81+
distance: configuration.distance,
82+
forceAdjacent: options.contains(.forceAdjacentValue)
83+
)
8484
}
8585
.onEnded { _ in
8686
configuration.dragOffset.wrappedValue = nil
@@ -104,7 +104,7 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu
104104
y: geometry.size.height / 2
105105
)
106106
.onTapGesture {
107-
self.onSelectUpper()
107+
self.onSelectUpper()
108108
}
109109
.gesture(
110110
DragGesture()
@@ -131,15 +131,14 @@ public struct HorizontalRangeSliderStyle<Track: View, LowerThumb: View, UpperThu
131131
leadingOffset: self.lowerThumbSize.width + self.upperThumbSize.width / 2,
132132
trailingOffset: self.upperThumbSize.width / 2
133133
)
134-
135-
if self.options.contains(.forceAdjacentValue) {
136-
let computedLowerBound = min(computedUpperBound, configuration.range.wrappedValue.lowerBound)
137-
configuration.range.wrappedValue = computedLowerBound...computedUpperBound
138-
} else {
139-
let computedUpperBound = max(computedUpperBound, configuration.range.wrappedValue.lowerBound)
140-
configuration.range.wrappedValue = configuration.range.wrappedValue.lowerBound...computedUpperBound
141-
}
142-
134+
135+
configuration.range.wrappedValue = rangeFrom(
136+
lowerBound: configuration.range.wrappedValue.lowerBound,
137+
updatedUpperBound: computedUpperBound,
138+
bounds: configuration.bounds,
139+
distance: configuration.distance,
140+
forceAdjacent: options.contains(.forceAdjacentValue)
141+
)
143142
}
144143
.onEnded { _ in
145144
configuration.dragOffset.wrappedValue = nil

Sources/Sliders/RangeSlider/Styles/Vertical/VerticalRangeSliderStyle.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ public struct VerticalRangeSliderStyle<Track: View, LowerThumb: View, UpperThumb
6565
leadingOffset: self.lowerThumbSize.height / 2,
6666
trailingOffset: self.lowerThumbSize.height / 2
6767
)
68-
69-
if self.options.contains(.forceAdjacentValue) {
70-
let computedUpperBound = max(computedLowerBound, configuration.range.wrappedValue.upperBound)
71-
configuration.range.wrappedValue = computedLowerBound...computedUpperBound
72-
} else {
73-
let computedLowerBound = min(computedLowerBound, configuration.range.wrappedValue.upperBound)
74-
configuration.range.wrappedValue = computedLowerBound...configuration.range.wrappedValue.upperBound
75-
}
68+
69+
configuration.range.wrappedValue = rangeFrom(
70+
updatedLowerBound: computedLowerBound,
71+
upperBound: configuration.range.wrappedValue.upperBound,
72+
bounds: configuration.bounds,
73+
distance: configuration.distance,
74+
forceAdjacent: options.contains(.forceAdjacentValue)
75+
)
7676
}
7777
.onEnded { _ in
7878
configuration.dragOffset.wrappedValue = nil
@@ -118,14 +118,14 @@ public struct VerticalRangeSliderStyle<Track: View, LowerThumb: View, UpperThumb
118118
leadingOffset: self.lowerThumbSize.height + self.upperThumbSize.height / 2,
119119
trailingOffset: self.upperThumbSize.height / 2
120120
)
121-
122-
if self.options.contains(.forceAdjacentValue) {
123-
let computedLowerBound = min(computedUpperBound, configuration.range.wrappedValue.lowerBound)
124-
configuration.range.wrappedValue = computedLowerBound...computedUpperBound
125-
} else {
126-
let computedUpperBound = max(computedUpperBound, configuration.range.wrappedValue.lowerBound)
127-
configuration.range.wrappedValue = configuration.range.wrappedValue.lowerBound...computedUpperBound
128-
}
121+
122+
configuration.range.wrappedValue = rangeFrom(
123+
lowerBound: configuration.range.wrappedValue.lowerBound,
124+
updatedUpperBound: computedUpperBound,
125+
bounds: configuration.bounds,
126+
distance: configuration.distance,
127+
forceAdjacent: options.contains(.forceAdjacentValue)
128+
)
129129
}
130130
.onEnded { _ in
131131
configuration.dragOffset.wrappedValue = nil

Sources/Sliders/ValueSlider/ValueSlider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ extension ValueSlider {
3535
}
3636

3737
extension ValueSlider {
38-
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 {
38+
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 {
3939
self.init(
4040
ValueSliderStyleConfiguration(
4141
value: Binding(get: { CGFloat(value.wrappedValue) }, set: { value.wrappedValue = V($0) }),

0 commit comments

Comments
 (0)