Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading