我正在尝试完成一个自定义滑块,它不是普通的滑块。它涉及通过在屏幕上拖动圆圈来按压内部和外部。圆圈的拖动量缩小了 10 倍。我已经完成了圆圈拖动部分,自定义滑块在拖动圆圈时的工作方式与我预期的一样。
但是,当我尝试通过拖动自定义滑块本身来逆转该过程时,问题就出现了。除了对 CustomSliderView 中的 .onChange(of: percentageDX) 部分进行逆向工程外,我已经完成了所有部分。我为此苦苦挣扎了几个小时,尝试了大约 50 次不同的反复试验,但我无法理解。我需要帮助来完成 CustomSliderView 中旋钮手势缺失的部分。
以下是代码工作原理的一些背景信息:我在应用程序中有一个屏幕,用作测量工具,用于找出圆圈阻力的百分比。然后,我将这个百分比发送给 CustomSliderView 并将其缩小 10 倍。此操作将滑块上的位移量减少了 10 倍,使其能够与圆圈的拖动一起正常工作。您可以尝试拖动圆圈,以查看当圆圈向左或向右的阻力或位移为零时,滑块为 100%。当圆圈开始向左或向右移动时,滑块开始向相反方向按压。
现在,我想要通过拖动滑块来逆转这个按下过程,让它在圆圈回到原来的位置时逐渐压回到 100%。
import SwiftUI
struct ContentView: View {
@State private var lastCircleOffset: CGSize = CGSize()
@State private var currentCircleOffset: CGSize = CGSize()
@State private var circleIsDragging: Bool = Bool()
@State private var dxKnobIsDragging: Bool = Bool()
@State private var percentageDX: CGFloat? = nil
private let maxScreenVisibilitySize: CGSize = CGSize(width: 400.0, height: 300.0)
var body: some View {
ZStack {
ZStack {
Color.clear
Color.white
.frame(width: maxScreenVisibilitySize.width, height: maxScreenVisibilitySize.height)
Circle()
.fill(.red)
.frame(width: 50.0, height: 50.0)
.offset(x: currentCircleOffset.width, y: currentCircleOffset.height)
.gesture(circleGesture)
if let unwrappedPercentageDX: CGFloat = percentageDX {
Text("percentageDX: \(String(format: "%.2f", unwrappedPercentageDX*100.0))%").bold().monospaced().foregroundStyle(.black).offset(y: 50)
}
}
.clipped()
.onChange(of: currentCircleOffset) { (oldValue, newValue) in
if (!dxKnobIsDragging) && (oldValue != newValue) {
percentageDX = (newValue.width/(maxScreenVisibilitySize.width/2.0))
}
}
.onChange(of: percentageDX) { (oldValue, newValue) in
if (!circleIsDragging) && (dxKnobIsDragging) {
if let unwrappedOldValue: CGFloat = oldValue, let unwrappedNewValue: CGFloat = newValue, (unwrappedOldValue != unwrappedNewValue) {
// I may made mistake here!
currentCircleOffset.width -= (unwrappedNewValue*maxScreenVisibilitySize.width)/2.0
}
}
}
VStack {
Spacer()
CustomSliderView(percentageDX: $percentageDX, dxKnobIsDragging: $dxKnobIsDragging)
}
}
.padding()
}
private var circleGesture: some Gesture {
return DragGesture(minimumDistance: CGFloat.zero, coordinateSpace: .local)
.onChanged() { value in
circleIsDragging = true
currentCircleOffset = CGSize(width: value.translation.width + lastCircleOffset.width,
height: value.translation.height + lastCircleOffset.height)
}
.onEnded() { value in
lastCircleOffset = currentCircleOffset
circleIsDragging = false
}
}
}
struct CGSizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize { get { return CGSize() } }
static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() }
}
struct CustomSliderView: View {
@Binding var percentageDX: CGFloat?
@Binding var dxKnobIsDragging: Bool
@State private var lastKnobOffset: CGFloat = CGFloat()
@State private var currentKnobOffset: CGFloat = CGFloat()
@State private var currentOffsetPercentage: CGFloat = CGFloat()
@State private var maxWidthSize: CGFloat = CGFloat()
@State private var knobWidth: CGFloat = CGFloat()
private let knobHeight: CGFloat = 25.0
var body: some View {
GeometryReader { proxy in
ZStack {
Color.gray
Color.white.opacity(knobWidth.isZero ? .zero : 0.85)
.frame(width: knobWidth)
.shadow(radius: 10)
.offset(x: currentKnobOffset)
.gesture(knobGesture)
}
.preference(key: CGSizePreferenceKey.self, value: proxy.size)
}
.frame(height: knobHeight)
.onPreferenceChange(CGSizePreferenceKey.self) { newValue in
maxWidthSize = newValue.width
if percentageDX == nil {
knobWidth = newValue.width
}
}
.onChange(of: percentageDX) { (oldValue, newValue) in
// This part is okay; it works as expected.
if (!dxKnobIsDragging) {
if let unwrappedOldValue: CGFloat = oldValue, let unwrappedNewValue: CGFloat = newValue, (unwrappedOldValue != unwrappedNewValue) {
if let unwrappedNewValue: CGFloat = newValue {
let newPercentageDX: CGFloat = abs(unwrappedNewValue)
let knobScale: CGFloat = 10.0
knobWidth = (maxWidthSize - (newPercentageDX*maxWidthSize)/knobScale)
if (unwrappedNewValue >= .zero) {
currentKnobOffset = -(newPercentageDX*maxWidthSize)/(2.0*knobScale)
lastKnobOffset = currentKnobOffset
}
else {
currentKnobOffset = (newPercentageDX*maxWidthSize)/(2.0*knobScale)
lastKnobOffset = currentKnobOffset
}
}
}
}
}
}
private var knobGesture: some Gesture {
return DragGesture(minimumDistance: CGFloat.zero, coordinateSpace: .local)
.onChanged() { value in
dxKnobIsDragging = true
// I need to finish this part with the help of reverse coding on .onChange(of: percentageDX)
}
.onEnded() { value in
lastKnobOffset = currentKnobOffset
dxKnobIsDragging = false
}
}
}
感谢您在评论中通过您的回答提供额外的信息。
据我从您的回答中了解到,滑块的宽度无需改变。它可以简单地占据整个可用宽度,然后根据百分比值进行偏移。
因此,我认为可以用一种更简单的方法来解决,使用更少的状态变量和更少的逻辑。
PreferenceKey
也不需要。GestureState
来记录基于当前拖动运动的百分比变化。使用的优点
GestureState
是当手势结束时它会自动重置回其初始值。这是您示例的更新版本。我希望它能按照您想要的方式运行。