swiftuidrag

DragGesture hangs SwiftUI after adapting simple example


I'm starting out using DragGestures in SwiftUI. I started with a simple internet example, did a couple modifications, everything was working fine. Here's that code:

struct DragTest: View {
  @State var viewState = CGSize.zero

  var body: some View {
    RoundedRectangle(cornerRadius: 30)
      .fill(Color.blue)
      .frame(width: 300, height: 400 + viewState.height)
      .offset(x:0, y: viewState.height / 2)
      .gesture(
        DragGesture().onChanged { value in
          viewState = value.translation
        }
          .onEnded { value in
            withAnimation(.spring()) {
              viewState = .zero
            }
          }
      )
  }
}

But if I change the frame height adjustment to subtract viewstate.height instead of add, the code will hang in preview or in the simulator with 100% processor usage. It seems like some sort of update loop.

struct DragTest: View {
  @State var viewState = CGSize.zero

  var body: some View {
    RoundedRectangle(cornerRadius: 30)
      .fill(Color.blue)
      .frame(width: 300, height: 400 - viewState.height)
      .offset(x:0, y: viewState.height / 2)
      .gesture(
        DragGesture().onChanged { value in
          viewState = value.translation
        }
          .onEnded { value in
            withAnimation(.spring()) {
              viewState = .zero
            }
          }
      )
  }
}

What is going on here? If I don't understand the root cause, I'm not going to get very far with drag gestures.


Solution

  • I think I've sorted this out, though the symptoms on the way to solution were more than confusing. It appears that if you plan on modifying the geometry of an object (.offset doesn't do this, but .frame does, it seems), then you need to perform the DragGesture in a different coordinate space which does not change size during the DragGesture. (What coordinate space does DragGesture normally operate in? - I don't know, and the documentation I found didn't say). Here is the code that works, plus I tried several variants.

    struct DragTest3: View {
      @State var trans = CGSize.zero
    
      var body: some View {
        Group {
          RoundedRectangle(cornerRadius: 30)
            .fill(Color.blue)
            .frame(width: 300, height: 400 - trans.height)
            .gesture(
              DragGesture(coordinateSpace: .named("outer"))
                .onChanged { value in
                  trans = value.translation
                }
            )
        }
        .frame(maxHeight: .infinity)
        .coordinateSpace(name: "outer")
        .border(.black)
      }
    }
    

    Most simple DragGesture examples on the internet only use the .offset modifier, so they don't crash, I couldn't easily find less trivial examples. My theory is that if the coordinate space in which the DragGesture is occurring is changing size, the code can enter a death spiral. It would be nice if the mechanisms were described in greater detail somewhere.