Skip to content

Instantly share code, notes, and snippets.

@hirohitokato
Created October 29, 2022 02:33
Show Gist options
  • Save hirohitokato/6b01287e104c3d0a49fc3f071be05e8f to your computer and use it in GitHub Desktop.
Save hirohitokato/6b01287e104c3d0a49fc3f071be05e8f to your computer and use it in GitHub Desktop.
//
// CustomSlider.swift
//
// @Refer: https://betterprogramming.pub/reusable-components-in-swiftui-custom-sliders-8c115914b856
// @Usage:
//
// CustomSlider(value: $state.exposureValue, range: (0, 1)) { modifiers in
// ZStack {
// LinearGradient(gradient: .init(colors: [Color.pink, Color.orange ]), startPoint: .leading, endPoint: .trailing)
// ZStack {
// Circle().fill(Color.white)
// Circle().stroke(Color.black.opacity(0.2), lineWidth: 2)
// Text(("\(state.exposureValue, specifier: "%.1F")")).font(.system(size: 11))
// }
// .padding([.top, .bottom], 2)
// .modifier(modifiers.knob)
// }.cornerRadius(15)
// }
// .frame(height: 30)
// .disabled(state.currentSelection == nil)
// .id(state.exposureValue)
//
import SwiftUI
struct CustomSliderComponents {
let barLeft: CustomSliderModifier
let barRight: CustomSliderModifier
let knob: CustomSliderModifier
}
struct CustomSliderModifier: ViewModifier {
enum Name {
case barLeft
case barRight
case knob
}
let name: Name
let size: CGSize
let offset: CGFloat
func body(content: Content) -> some View {
content
.frame(width: size.width)
.position(x: size.width*0.5, y: size.height*0.5)
.offset(x: offset)
}
}
fileprivate extension Float {
func convert(fromRange: (Float, Float), toRange: (Float, Float)) -> Float {
// Example: if self = 1, fromRange = (0,2), toRange = (10,12) -> solution = 11
var value = self
value -= fromRange.0
value /= Float(fromRange.1 - fromRange.0)
value *= toRange.1 - toRange.0
value += toRange.0
return value
}
}
struct CustomSlider<Component: View>: View {
@Binding var value: Float
var range: (Float, Float)
var knobWidth: CGFloat?
let viewBuilder: (CustomSliderComponents) -> Component
init(value: Binding<Float>, range: (Float, Float), knobWidth: CGFloat? = nil,
_ viewBuilder: @escaping (CustomSliderComponents) -> Component
) {
_value = value
self.range = range
self.viewBuilder = viewBuilder
self.knobWidth = knobWidth
}
var body: some View {
return GeometryReader { geometry in
self.view(geometry: geometry) // function below
}
}
private func view(geometry: GeometryProxy) -> some View {
let frame = geometry.frame(in: .global)
let drag = DragGesture(minimumDistance: 0).onChanged({ drag in
self.onDragChange(drag, frame) }
)
let offsetX = self.getOffsetX(frame: frame)
let knobSize = CGSize(width: knobWidth ?? frame.height, height: frame.height)
let barLeftSize = CGSize(width: CGFloat(offsetX + knobSize.width * 0.5), height: frame.height)
let barRightSize = CGSize(width: frame.width - barLeftSize.width, height: frame.height)
let modifiers = CustomSliderComponents(
barLeft: CustomSliderModifier(name: .barLeft, size: barLeftSize, offset: 0),
barRight: CustomSliderModifier(name: .barRight, size: barRightSize, offset: barLeftSize.width),
knob: CustomSliderModifier(name: .knob, size: knobSize, offset: offsetX))
return ZStack { viewBuilder(modifiers).gesture(drag) }
}
private func onDragChange(_ drag: DragGesture.Value,_ frame: CGRect) {
let width = (knob: Float(knobWidth ?? frame.size.height), view: Float(frame.size.width))
let xrange = (min: Float(0), max: Float(width.view - width.knob))
var value = Float(drag.startLocation.x + drag.translation.width) // knob center x
value -= 0.5*width.knob // offset from center to leading edge of knob
value = value > xrange.max ? xrange.max : value // limit to leading edge
value = value < xrange.min ? xrange.min : value // limit to trailing edge
value = value.convert(fromRange: (xrange.min, xrange.max), toRange: range)
self.value = value
}
private func getOffsetX(frame: CGRect) -> CGFloat {
let width = (knob: knobWidth ?? frame.size.height, view: frame.size.width)
let xrange: (Float, Float) = (0, Float(width.view - width.knob))
let result = self.value.convert(fromRange: range, toRange: xrange)
return CGFloat(result)
}
}
@hirohitokato
Copy link
Author

SwiftUIでいい感じにスライダーを作るサンプル。 このページから作成。

Usageを実行すると↓のように表示される。

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