Skip to content

Commit

Permalink
Merge pull request #1 from swift-extensions/styles
Browse files Browse the repository at this point in the history
Experimental slider
  • Loading branch information
ay42 authored Jan 26, 2020
2 parents 226561c + 6ee7ebc commit c603297
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 12 deletions.
33 changes: 23 additions & 10 deletions Examples/SlidersExamples/HorizontalSliderExamplesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,21 @@ struct HorizontalSliderExamplesView: View {

var body: some View {
ScrollView {
Group {
Group {

HSlider(value: $model.value1)

// ValueSlider(value: $model.value1)
// .background(Color.yellow)
// .valueSliderStyle(
// HorizontalValueSliderStyle(
// track: { HorizontalValueTrack(value: $0) },
// thumbSize: CGSize(width: 32, height: 32),
// options: .interactiveTrack
// )
// )


HSlider(value: $model.value2,
configuration: .init(
thumbSize: CGSize(width: 16, height: 32)
Expand Down Expand Up @@ -116,16 +128,17 @@ struct HorizontalSliderExamplesView: View {

HRangeSlider(
range: $model.range3,
track: HRangeTrack(
range: model.range3,
view: LinearGradient(gradient: Gradient(colors: [.red, .orange, .yellow, .green, .blue, .purple, .pink]), startPoint: .leading, endPoint: .trailing),
configuration: .init(
offsets: 32
track:
HRangeTrack(
range: model.range3,
view: LinearGradient(gradient: Gradient(colors: [.red, .orange, .yellow, .green, .blue, .purple, .pink]), startPoint: .leading, endPoint: .trailing),
configuration: .init(
offsets: 32
)
)
)
.background(LinearGradient(gradient: Gradient(colors: [.red, .orange, .yellow, .green, .blue, .purple, .pink]), startPoint: .leading, endPoint: .trailing).opacity(0.25))
.frame(height: 32)
.cornerRadius(16),
.background(LinearGradient(gradient: Gradient(colors: [.red, .orange, .yellow, .green, .blue, .purple, .pink]), startPoint: .leading, endPoint: .trailing).opacity(0.25))
.frame(height: 32)
.cornerRadius(16),
lowerThumb: HalfCapsule().foregroundColor(.white).shadow(radius: 3),
upperThumb: HalfCapsule().rotation(Angle(degrees: 180)).foregroundColor(.white).shadow(radius: 3),
configuration: .init(
Expand Down
2 changes: 0 additions & 2 deletions Sources/Sliders/Base/DefaultThumb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ public extension CGSize {
static let defaultThumbInteractiveSize : CGSize = CGSize(width: 44, height: 44)
}

#if DEBUG
struct DefaultThumb_Previews: PreviewProvider {
static var previews: some View {
DefaultThumb()
.previewLayout(.fixed(width: 100, height: 100))
}
}
#endif
61 changes: 61 additions & 0 deletions Sources/Sliders/ValueSlider/ValueSlider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import SwiftUI

public struct ValueSlider<Track>: View where Track: View {
@Environment(\.valueSliderStyle) private var style
@State private var dragOffset: CGFloat?

private var configuration: ValueSliderStyleConfiguration

public var body: some View {
self.style.makeBody(configuration:
self.configuration.with(dragOffset: self.$dragOffset)
)
}
}

extension ValueSlider {
init(_ configuration: ValueSliderStyleConfiguration) {
self.configuration = configuration
}
}

extension ValueSlider where Track == ValueSliderStyleConfiguration.Track {
public init<V>(value: Binding<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 {

self.init(
ValueSliderStyleConfiguration(
value: Binding(get: { CGFloat(value.wrappedValue) }, set: { value.wrappedValue = V($0) }),
bounds: CGFloat(bounds.lowerBound)...CGFloat(bounds.upperBound),
step: CGFloat(step),
onEditingChanged: onEditingChanged,
dragOffset: .constant(0),
track: .init(view: DefaultHorizontalValueTrack(value: CGFloat(value.wrappedValue))),
thumb: .init(view: DefaultThumb())
)
)
}
}

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

self.init(
ValueSliderStyleConfiguration(
value: Binding(get: { CGFloat(value.wrappedValue) }, set: { value.wrappedValue = V($0) }),
bounds: CGFloat(bounds.lowerBound)...CGFloat(bounds.upperBound),
step: CGFloat(step),
onEditingChanged: onEditingChanged,
dragOffset: .constant(0),
track: .init(view: track),
thumb: .init(view: DefaultThumb())
)
)
}
}

struct ValueSlider_Previews: PreviewProvider {
static var previews: some View {
ValueSlider(value: .constant(0.3))
.previewLayout(.fixed(width: 300, height: 100))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import SwiftUI

public struct HorizontalValueSliderStyle<Track: View>: ValueSliderStyle {
private let track: (CGFloat) -> Track
private let thumbSize: CGSize
private let thumbInteractiveSize: CGSize
private let options: HorizontalValueSliderOptions

public func makeBody(configuration: Self.Configuration) -> some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
if self.options.contains(.interactiveTrack) {
self.track(configuration.value.wrappedValue)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { gestureValue in
let computedValue = valueFrom(
distance: gestureValue.location.x,
availableDistance: geometry.size.width,
bounds: configuration.bounds,
step: configuration.step,
leadingOffset: self.thumbSize.width / 2,
trailingOffset: self.thumbSize.width / 2
)
configuration.value.wrappedValue = computedValue
configuration.onEditingChanged(true)
}
.onEnded { _ in
configuration.onEditingChanged(false)
}
)
} else {
self.track(configuration.value.wrappedValue)
}

ZStack {
configuration.thumb
.frame(width: self.thumbSize.width, height: self.thumbSize.height)
}
.frame(minWidth: self.thumbInteractiveSize.width, minHeight: self.thumbInteractiveSize.height)

.position(
x: distanceFrom(
value: configuration.value.wrappedValue,
availableDistance: geometry.size.width,
bounds: configuration.bounds,
leadingOffset: self.thumbSize.width / 2,
trailingOffset: self.thumbSize.width / 2
),
y: geometry.size.height / 2
)
.gesture(
DragGesture()
.onChanged { gestureValue in
if configuration.dragOffset.wrappedValue == nil {
configuration.dragOffset.wrappedValue = gestureValue.startLocation.x - distanceFrom(
value: configuration.value.wrappedValue,
availableDistance: geometry.size.width,
bounds: configuration.bounds,
leadingOffset: self.thumbSize.width / 2,
trailingOffset: self.thumbSize.width / 2
)
}

let computedValue = valueFrom(
distance: gestureValue.location.x - (configuration.dragOffset.wrappedValue ?? 0),
availableDistance: geometry.size.width,
bounds: configuration.bounds,
step: configuration.step,
leadingOffset: self.thumbSize.width / 2,
trailingOffset: self.thumbSize.width / 2
)

configuration.value.wrappedValue = computedValue
configuration.onEditingChanged(true)
}
.onEnded { _ in
configuration.dragOffset.wrappedValue = nil
configuration.onEditingChanged(false)
}
)
}

}
.frame(minHeight: self.thumbInteractiveSize.height)
}

public init(@ViewBuilder track: @escaping (CGFloat) -> Track, thumbSize: CGSize = CGSize(width: 32, height: 32), thumbInteractiveSize: CGSize = CGSize(width: 44, height: 44), options: HorizontalValueSliderOptions = .defaultOptions) {
self.track = track
self.thumbSize = thumbSize
self.thumbInteractiveSize = thumbInteractiveSize
self.options = options
}
}

public struct HorizontalValueSliderOptions: OptionSet {
public let rawValue: Int

public static let interactiveTrack = HorizontalValueSliderOptions(rawValue: 1 << 0)
public static let defaultOptions: HorizontalValueSliderOptions = []

public init(rawValue: Int) {
self.rawValue = rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import SwiftUI

struct AnyValueSliderStyle: ValueSliderStyle {
private let styleMakeBody: (ValueSliderStyle.Configuration) -> AnyView

init<S: ValueSliderStyle>(_ style: S) {
self.styleMakeBody = style.makeTypeErasedBody
}

func makeBody(configuration: ValueSliderStyle.Configuration) -> AnyView {
self.styleMakeBody(configuration)
}
}

fileprivate extension ValueSliderStyle {
func makeTypeErasedBody(configuration: ValueSliderStyle.Configuration) -> AnyView {
AnyView(makeBody(configuration: configuration))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import SwiftUI

extension EnvironmentValues {
var valueSliderStyle: AnyValueSliderStyle {
get {
return self[ValueSliderStyleKey.self]
}
set {
self[ValueSliderStyleKey.self] = newValue
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SwiftUI

/// Defines the implementation of all `ValueSlider` instances within a view
/// hierarchy.
///
/// To configure the current `ValueSlider` for a view hiearchy, use the
/// `.valueSliderStyle()` modifier.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ValueSliderStyle {
/// A `View` representing the body of a `ValueSlider`.
associatedtype Body : View

/// Creates a `View` representing the body of a `ValueSlider`.
///
/// - Parameter configuration: The properties of the value slider instance being
/// created.
///
/// This method will be called for each instance of `ValueSlider` created within
/// a view hierarchy where this style is the current `ValueSliderStyle`.
func makeBody(configuration: Self.Configuration) -> Self.Body

/// The properties of a `ValueSlider` instance being created.
typealias Configuration = ValueSliderStyleConfiguration
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import SwiftUI

public struct ValueSliderStyleConfiguration {
public let value: Binding<CGFloat>
public let bounds: ClosedRange<CGFloat>
public let step: CGFloat
public let onEditingChanged: (Bool) -> Void
public var dragOffset: Binding<CGFloat?>
public let track: ValueSliderStyleConfiguration.Track
public let thumb: ValueSliderStyleConfiguration.Thumb

func with(dragOffset: Binding<CGFloat?>) -> Self {
var mutSelf = self
mutSelf.dragOffset = dragOffset
return mutSelf
}
}

public extension ValueSliderStyleConfiguration {
struct Track: View {
let typeErasedTrack: AnyView

init<T: View>(view: T) {
self.typeErasedTrack = AnyView(view)
}

public var body: some View {
self.typeErasedTrack
}
}
}

public extension ValueSliderStyleConfiguration {
struct Thumb: View {
let typeErasedThumb: AnyView

init<T: View>(view: T) {
self.typeErasedThumb = AnyView(view)
}

public var body: some View {
self.typeErasedThumb
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import SwiftUI

struct ValueSliderStyleKey: EnvironmentKey {
static let defaultValue: AnyValueSliderStyle = AnyValueSliderStyle(HorizontalValueSliderStyle(track: { _ in Capsule() }))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import SwiftUI

extension View {
/// Sets the style for `ValueSlider` within the environment of `self`.
public func valueSliderStyle<S>(_ style: S) -> some View where S : ValueSliderStyle {
self.environment(\.valueSliderStyle, AnyValueSliderStyle(style))
}
}
8 changes: 8 additions & 0 deletions Sources/Sliders/ValueTrack/ValueTrack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// File.swift
//
//
// Created by Alex on 2019-11-07.
//

import Foundation

0 comments on commit c603297

Please sign in to comment.