Search code examples
swiftdebuggingswiftuidrag

How to edit existing value with drag gesture?


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
          }
        }

      }
    )

Cannot start from existing value!!

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?


Solution

  • 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...