skip to Main Content

How to detect drag events for the stack when drag action have started outside of the stack view?

On the screen are placed the main stack/view (GREEN) and sub-stack/view (RED). I need to implement functionality which detects swipe/drag interaction for the GREEN view. Here are two scenarios and the second is failing:

FAILING OK
When the drag gesture starts outside (on the GREEN view) of the RED view and then swiping over RED view onChange event for RED view can not be retrieved. When the drag gesture starts on the RED view onChange events are retrieved and drag gesture can be tracked.
enter image description here enter image description here

How to detect drag events when drag have started outside of the stack view?

I have tried many things over the week. Some of them are:

  • I have tried setting coordinate space for drag coordinates, and some views.
  • Tried adding multiple simultaneous gesture recognizers.

Sample code which can be added to Xcode Playgrounds or demo app.

import PlaygroundSupport
import SwiftUI
PlaygroundPage.current.needsIndefiniteExecution = true

struct DemoView: View {
    var body: some View {
        VStack {
            VStack {
                Text("3")
            }
            .id("RED")
            .frame(width: 200, height: 300, alignment: .center)
            .background(.red)
            .simultaneousGesture(
                DragGesture()
                    .onChanged({
                        print("🔴", $0.location)
                    })
            )
        }
        .id("GREEN")
        .frame(width: 400, height: 700, alignment: .center)
        .background(.green)
        .simultaneousGesture(
            DragGesture()
                .onChanged({
                    print("🟢", $0.location)
                })
        )
    }
}

// Write grean rectangle 

let view =  DemoView()
PlaygroundPage.current.setLiveView(view)

2

Answers


  1. Using an overlay in this version it detects red drag [exclusively] if you start in red and green [also exclusively] if you start in green.

    import SwiftUI
    
    struct ContentView: View {
      var body: some View {
        VStack {
            DemoView()
        }
        .padding()
      }
    }
    
    struct DemoView: View {
      var body: some View {
        VStack {
            
        }
        .id("GREEN")
        .frame(width: 400, height: 700, alignment: .center)
        .background(.green)
        .simultaneousGesture(
            DragGesture(minimumDistance: 0)
                .onChanged({
                    print("🟢", $0.location)
                })
        ).overlay {
            VStack {
                Text("3")
            }
            .id("RED")
            .frame(width: 200, height: 300, alignment: .center)
            .background(.red)
            .simultaneousGesture(
                DragGesture()
                    .onChanged({
                        print("🔴", $0.location)
                    })
            )
        }
      }
    }
    

    So if I comment out the green gesture and use the coordinates returned, I can use the coordinates returned to work out if I am in red or green within the "red" drag gesture.

              .simultaneousGesture(
                DragGesture()
                    .onChanged({
                        if $0.location.x < 0 ||
                            $0.location.y < 0 ||
                            $0.location.x > 200 ||
                            $0.location.y > 300 {
                            print("🟢", $0.location)
                        } else {
                            print("🔴", $0.location)
                        }
                    })
            )
    

    Of course, if you start within the green, nothing is reported. But start within the red, and you get both.

    You can now uncomment the green gesture and use the same sort of logic, within its gesture.

        .simultaneousGesture(
            DragGesture(minimumDistance: 0)
                .onChanged({
                    if ($0.location.x < 300 && $0.location.x > 100) {
                        if ($0.location.y < 500 && $0.location.y > 200 ) {
                            print("🔴", $0.location)
                        } else {
                            print("🟢", $0.location)
                        }
                    } else {
                        print("🟢", $0.location)
                    }
                })
        )
    

    And there you have it; if you start on green it will work and if you start on red it will work.

    Login or Signup to reply.
    1. Make a container to have a same coordinate system for all views
    2. Find frames for all desired views in that container
    3. Perform actions if the drag location is inside the desired frame in that coordinate system
    struct DemoView: View {
        @State private var greenFrame: CGRect = .zero
        @State private var redFrame: CGRect = .zero
    
        var body: some View {
            ZStack {
                Color.green
                    .frame(width: 400, height: 400, alignment: .center)
                    .background { GeometryReader { p in
                        Spacer().onAppear() { greenFrame = p.frame(in: .named("CONTAINER")) }
                    }}
                Color.red
                    .frame(width: 200, height: 200, alignment: .center)
                    .background { GeometryReader { p in
                        Spacer().onAppear { redFrame = p.frame(in: .named("CONTAINER")) }
                    }}
            }
            .coordinateSpace(name: "CONTAINER")
            .gesture(DragGesture()
                .onChanged {
                    if redFrame.contains($0.location) { print("🔴", $0.location) }
                    if greenFrame.contains($0.location) { print("🟢", $0.location) }
                }
            )
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search