3

I am showing a camera preview view on a .sheet.

I need to detect when the user's finger touches the preview area and when the user's finger leaves the preview area.

The only solution I have found to do that was this

  CameraPreview()
    .gesture(DragGesture(minimumDistance: 0)
                          .onChanged { _ in
                            // finger touches
                          }
                          .onEnded { _ in
                            // finger leaves
                          })

but as this preview is being displayed on a sheet and sheets must be closed by dragging them from the top down, this drag gesture prevents the sheet drag gesture from working.

Any ideas?

NOTE: I do not need to detect dragging. I need to detect single tap on a view and then receive a callback when the finger leaves that view.

Duck
  • 34,902
  • 47
  • 248
  • 470

1 Answers1

1

You need to use UIViewRepresentable for this. Here are the steps:

  1. Create a custom version of AnyGestureRecognizer taken from here:
class AnyGestureRecognizer: UIGestureRecognizer {
    var onCallback: () -> Void
    var offCallback: () -> Void

    init(target: Any?, onCallback: @escaping () -> Void, offCallback: @escaping () -> Void) {
        self.onCallback = onCallback
        self.offCallback = offCallback
        super.init(target: target, action: nil)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        if let touchedView = touches.first?.view, touchedView is UIControl {
            state = .cancelled
            offCallback()
        } else if let touchedView = touches.first?.view as? UITextView, touchedView.isEditable {
            state = .cancelled
            offCallback()
        } else {
            state = .began
            onCallback()
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        state = .ended
        offCallback()
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
        state = .cancelled
        offCallback()
    }
}
  1. Create a custom UIViewRepresentable view and attach the AnyGestureRecognizer to it:
struct GestureView: UIViewRepresentable {
    var onCallback: () -> Void
    var offCallback: () -> Void

    func makeUIView(context: UIViewRepresentableContext<GestureView>) -> UIView {
        let view = UIView(frame: .zero)
        let gesture = AnyGestureRecognizer(
            target: context.coordinator,
            onCallback: onCallback,
            offCallback: offCallback
        )
        gesture.delegate = context.coordinator
        view.addGestureRecognizer(gesture)
        return view
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<GestureView>) {}

    class Coordinator: NSObject {}

    func makeCoordinator() -> GestureView.Coordinator {
        Coordinator()
    }
}

extension GestureView.Coordinator: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        true
    }
}
  1. Use it as an overlay:
struct ContentView: View {
    var body: some View {
        Color.blue
            .sheet(isPresented: .constant(true)) {
                Color.red
                    .overlay(
                        GestureView(onCallback: { print("start") }, offCallback: { print("end") })
                    )
            }
    }
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • Thanks but unfortunately your code does not work as expected. If I tap and hold for a little bit. It will never detect the finger touching the button. Your code works If I tap fast, then it will trigger both actions in sequence. – Duck Feb 12 '21 at 08:52
  • @Duck You can replace `TapGesture()` with `LongPressGesture(minimumDuration: 0)` - see updated answer. – pawello2222 Feb 12 '21 at 09:02
  • Now touch down/up are detected correctly but we are back to square one, because if the view this is in is displayed on a .sheet the .sheet stops responding to the drag gesture, what is the point of the question. So, it is impossible to close the sheet by dragging it down. I can add a close button, but I am sure my boss will complain... – Duck Feb 12 '21 at 09:04
  • if there is no solution for that I will have to add a close button. :( – Duck Feb 12 '21 at 09:06