Search code examples
iosswiftxcodeswiftui

How to get a drag gesture when a drag event starts out of the stack view?


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)

Solution

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