10

I have view hierarchy in SwiftUI like

ParentView { 
//other views

ChildView().highPriorityGesture(TapGesture().onEnded {
                        print("Tap!")
                    })
// other views 
}self.gesture(tap)

And I want to have parent view handle all taps on the screen in spite of cases when user taps onto ChildView. Now both closures executes. How to stop tap gesture events propagating up view hierarchy?

Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143
  • A bit confused... Why then you added `highPriorityGesture` to `ChildView` if you don't want to handle it? – Asperi Jan 28 '20 at 10:04
  • 1
    I do not want to Parent View has gesture recognized if tap is over Child view Rectangle. Parent View covers entire screen ex List and Child View is just cell in this list. I want to be tap recognized on entire screen inspite of this single Child View Cell. I consider addition of higher priority gesture block lower priority gesture but it doesn't work this way. – Michał Ziobro Jan 28 '20 at 10:10

2 Answers2

8

Well, probably there is some specific in which exactly ChildView and ParentView, because as tested below (Xcode 11.2 / iOS 13.2) child view gesture just overrides parent view gesture.

Here is demo.. tapped in yellow area, then tapped in green area - no mix callbacks

enter image description here

Complete module code

import SwiftUI

struct TestGesturesPriority: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
                .padding()
                .background(Color.yellow)
                .gesture(TapGesture().onEnded {
                    print(" -- child")
                })
        }
        .frame(width: 400, height: 400)
        .background(Color.green)
        .gesture(TapGesture().onEnded {
            print(">> parent")
        })
    }
}

Update: variant for List-Row

Yeees... List (Parent) - Row (Child) case appeared very challenging... please find below approach, it looks weird but tested & works

struct TestGesturesPriority: View {

    let parentGesture = TapGesture().onEnded { // just for convenience
        print(">> parent")
    }
    
    @GestureState private var captured = false
    var body: some View {
        List {
            Text("Hello, World!").padding()
                    .background(Color.yellow)
                    .allowsHitTesting(true)
                    .gesture(DragGesture(minimumDistance: 0) // mimic Tap
                        .updating($captured, body: { (value, state, transaction) in
                        state = true // mark captured (will be reset automatically)
                    })
                    .onEnded { value in
                        // like Tap, but can be ignored if delta 
                        // is large or out of view
                        print(" -- child")
                    }
                )
        }
        .gesture(parentGesture, including: captured ? .subviews : .gesture)
    }
}

To summarise - actually I think it is another List defect

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Maybe inside List (Parent) > Row (Child) something works differently? – Michał Ziobro Jan 28 '20 at 13:55
  • @MichałZiobro, see **Update** part – Asperi Jan 28 '20 at 17:12
  • 1
    It is crazy that getting the child to cancel the gesture from traversing to the parent is **this** difficult -- but you saved the day! – Mark Sep 19 '21 at 13:35
  • I cannot get the same behaviour with Map and it's annotation. Meaning: I tap the annotation, the it first triggers the Map's onTap action, then triggers the annotation's onTap action. Any ideas about that? – Gergely Kovacs Dec 04 '22 at 12:31
3

There's a slightly cleaner way to solve the tap localisation issue in List as follows:

struct TestListGestures: View {

    var body: some View {
        List {
            Text("Hello, World!").padding()
                    .background(Color.yellow)

              .gesture(LongPressGesture(minimumDuration: 0.001) // resolve response time
                    .onEnded { value in
                        print(" -- child")
                    }
                )
        }
        .gesture(LongPressGesture(minimumDuration: 0.001).onEnded({ _ in
          print(" -- parent")
        }), including: .gesture)
    }
}
Pranav Kasetti
  • 8,770
  • 2
  • 50
  • 71