Created
October 29, 2022 02:33
-
-
Save hirohitokato/6b01287e104c3d0a49fc3f071be05e8f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
SwiftUIでいい感じにスライダーを作るサンプル。 このページから作成。
Usageを実行すると↓のように表示される。