I am writing a view that enables choosing pan values by dragging across a left/right level diagram. It works perfectly except the user always has to choose the value from scratch, they cannot adjust an existing value.
.gesture(
withAnimation(.easeInOut){
DragGesture()
.onChanged { dval in
withAnimation(.easeInOut(duration: 0.3)) {
self.valViewOpacity = 1.0
}
let drag = dval.translation.width * 0.35
let newValue = min(max((drag), -50), 50)
self.pan = newValue
}
.onEnded { value in
withAnimation(.easeInOut(duration: 2)) {
self.valViewOpacity = 0.0
}
}
}
)
As you can see, I've slowed down the drag sensitivity by reducing the drag value to 1/3 its width. Logically, the progression of values should be 0, .35, .7, 1...
so if I ended the last drag on a value of 10, I'd have expected the progression to be 10, 10.35, 10.7, 11...
But it's not. It goes 10, 0, 50
!
My attempt at integrating the pre-existing value into the code was to modify the 2nd line in the onChanged
closure to this:
let drag = dval.translation.width * 0.35 + self.pan
I moved it around and used parens, but basically whenever I put + self.pan
anywhere in the code, the value snaps to -50 or 50 as soon as I start dragging. I realized that continual adding is the wrong logic, so tried to find a place to do it only one time at the beginning, but I cannot make it stick, obviously the drag value is always absolute.
Is this a threading issue? I'm watching the value using a Text() view:
private var valView: some View {
Text("\(self.pan)")
.bold()
.font(.custom("Varitronix", size: 39.0))
.foregroundColor(.green)
}
How can I get my drag-created values to start at the existing value and change it from there?
I realized that what was happening was that the value of pan
was increasing, so if I used
let drag = dval.translation.width * 0.35 + self.pan
I was adding the increasing value back to itself, thus increasing it exponentially !
What I did to solve this was to copy pan
's initial value into panStartedAt
and add this into the expression. This gave me the offset I started with and from then on, the values were properly set. Upon drag.end, pan
has a new value, so the next drag operation starts from there! Voila!
.gesture(
DragGesture(minimumDistance: 0.0)
.onChanged { dval in
withAnimation(.easeInOut(duration: 0.3)) {
self.valViewOpacity = 1.0 // Fade in when dragging starts
}
if( first ) {
self.panStartedAt = pan
first = false
}
let drag = dval.translation.width * 0.35
let newValue = min(max((Int(drag) + self.panStartedAt), -50), 50)
self.pan = newValue
}
.onEnded { value in
withAnimation(.easeInOut(duration: 2)) {
self.valViewOpacity = 0.0
}
first = true
}
)
The only thing I'm not particularly proud of is using the boolean
- I'd much prefer something more organic than flag-bound logic. Oh well, at least it runs perfectly...