Animating a view on touch?

I'm having issues trying to emulate what was previously handled with an onTouchDownInside event.
I'm trying to make a Circle change colors while being touched and change back when the touch stops.
I've tried using longPressAction:

struct NodeConnector : View {
  @State var beingTouched = false
  
  var body: some View {
    let circle = Circle()
      .frame(width: 20, height: 20)
      .longPressAction(minimumDuration: 1000000, maximumDistance: 0, { }, pressing: { pressed in
        self.beingTouched = pressed
      })
    
    return VStack {
      if beingTouched {
        circle
          .foregroundColor(.green)
        
      } else {
        circle
          .foregroundColor(.black)
        
      }
    }
  }
}

That causes the circle to turn green for a second and then it turns back (before the finger is lifted).
I've also tried using DragGesture:

struct NodeConnector : View {
  @State var beingTouched = false
  
  var body: some View {
    let dragGesture = DragGesture(minimumDistance: 0.0)
      .onChanged { _ in self.beingTouched = true }
      .onEnded { _ in self.beingTouched = false }
    
    let circle = Circle()
      .frame(width: 20, height: 20)
      .gesture(dragGesture)
      .animation(.basic())
    
    return VStack {
      if beingTouched {
        circle
          .foregroundColor(.green)
        
      } else {
        circle
          .foregroundColor(.black)
        
      }
    }
  }
}

That never stops being green (never firing onEnded - even after the finger is lifted)



From experimentation, I imagine that the Circle is being destroyed and recreated upon the state changing and thus the gesture never completes?


Does anyone know what I'm doing wrong, or a workaround?

Answered by fingatech in 369935022

Figured it out!
This works:

struct ContentView : View {
  @State var beingTouched = false
  
  var body: some View {
    let dragGesture = DragGesture(minimumDistance: 0.0)
      .onChanged { _ in self.beingTouched = true }
      .onEnded { _ in self.beingTouched = false }
    
    let circle = Circle()
      .frame(width: 20, height: 20)
      .gesture(dragGesture)
      .animation(.basic())
    
    return VStack {
      circle
        .foregroundColor(beingTouched ? .green : .black)
    }
  }
}



This does not:

struct ContentView : View {
  @State var beingTouched = false
  
  var body: some View {
    let dragGesture = DragGesture(minimumDistance: 0.0)
      .onChanged { _ in self.beingTouched = true }
      .onEnded { _ in self.beingTouched = false }
    
    let circle = Circle()
      .frame(width: 20, height: 20)
      .gesture(dragGesture)
      .animation(.basic())
    
    return VStack {
      if beingTouched {
        circle
          .foregroundColor(.green)
        
      } else {
        circle
          .foregroundColor(.black)
        
      }
    }
  }
}

My initial guess is:
There is some algorithm that determines if the same circle is being displayed or if it needs to recreate the circle.


If it recreates the circle, it breaks (abandons) the ongoing gesture. If it detects that it is the same circle, it keeps the gesture live while redrawing the circle.
A question that raises would be - is there a way for me to tell the algorithm that this is the same circle, within both branches of the if statement? But - the initial question of this post has been answered!

Accepted Answer

Figured it out!
This works:

struct ContentView : View {
  @State var beingTouched = false
  
  var body: some View {
    let dragGesture = DragGesture(minimumDistance: 0.0)
      .onChanged { _ in self.beingTouched = true }
      .onEnded { _ in self.beingTouched = false }
    
    let circle = Circle()
      .frame(width: 20, height: 20)
      .gesture(dragGesture)
      .animation(.basic())
    
    return VStack {
      circle
        .foregroundColor(beingTouched ? .green : .black)
    }
  }
}



This does not:

struct ContentView : View {
  @State var beingTouched = false
  
  var body: some View {
    let dragGesture = DragGesture(minimumDistance: 0.0)
      .onChanged { _ in self.beingTouched = true }
      .onEnded { _ in self.beingTouched = false }
    
    let circle = Circle()
      .frame(width: 20, height: 20)
      .gesture(dragGesture)
      .animation(.basic())
    
    return VStack {
      if beingTouched {
        circle
          .foregroundColor(.green)
        
      } else {
        circle
          .foregroundColor(.black)
        
      }
    }
  }
}

My initial guess is:
There is some algorithm that determines if the same circle is being displayed or if it needs to recreate the circle.


If it recreates the circle, it breaks (abandons) the ongoing gesture. If it detects that it is the same circle, it keeps the gesture live while redrawing the circle.
A question that raises would be - is there a way for me to tell the algorithm that this is the same circle, within both branches of the if statement? But - the initial question of this post has been answered!

Animating a view on touch?
 
 
Q