I'm trying to implement a custom context menu that will appear after a long press at the location the user touched. I have been unable to find a way to capture the XY location of a touch down event for onLongPressGesture.
This is where I started
struct ExampleView: View {
@State var showCustomContextMenu = false
@State var longPressLocation = CGPoint.zero
var body: some View {
Rectangle()
.foregroundColor(Color.green)
.frame(width: 100.0, height: 100.0)
.onLongPressGesture {
print("OnLongPressGesture")
self.showCustomContextMenu = true
}
.overlay(
Rectangle()
.foregroundColor(Color.red)
.frame(width: 50.0, height: 50.0)
.position(longPressLocation) // <----- this is what I need to capture.
.opacity( (showCustomContextMenu) ? 1 : 0 )
)
}
}
After looking at this question (and the other SO questions linked in the answer) I tried the following.
How do you detect a SwiftUI touchDown event with no movement or duration?
struct ExampleView: View {
@State var showCustomContextMenu = false
@State var longPressLocation = CGPoint.zero
var body: some View {
ZStack{
Rectangle()
.foregroundColor(Color.green)
.frame(width: 100.0, height: 100.0)
.onLongPressGesture {
print("OnLongPressGesture")
self.showCustomContextMenu = true
}
.overlay(
Rectangle()
.foregroundColor(Color.red)
.frame(width: 50.0, height: 50.0)
.position(longPressLocation)
.opacity( (showCustomContextMenu) ? 1 : 0 )
)
TapView { point in
self.longPressLocation = point
print("Point: \(point)")
}.background(Color.gray).opacity(0.5)
}
}
}
struct TapView: UIViewRepresentable {
var tappedCallback: ((CGPoint) -> Void)
func makeUIView(context: UIViewRepresentableContext<TapView>) -> TapView.UIViewType {
let v = UIView(frame: .zero)
let gesture = SingleTouchDownGestureRecognizer(target: context.coordinator,
action: #selector(Coordinator.tapped))
v.addGestureRecognizer(gesture)
return v
}
class Coordinator: NSObject {
var tappedCallback: ((CGPoint) -> Void)
init(tappedCallback: @escaping ((CGPoint) -> Void)) {
self.tappedCallback = tappedCallback
}
@objc func tapped(gesture:UITapGestureRecognizer) {
self.tappedCallback(gesture.location(in: gesture.view))
}
}
func makeCoordinator() -> TapView.Coordinator {
return Coordinator(tappedCallback:self.tappedCallback)
}
func updateUIView(_ uiView: UIView,
context: UIViewRepresentableContext<TapView>) {
}
}
class SingleTouchDownGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if self.state == .possible {
self.state = .recognized
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
}
This almost works, however, the problem this leaves me with is that since Rectangle() and TapView() are in a ZStack depending on where I place them in code I get either the touchDown location or the onLongPressGesture but not both.
Other SO questions I've looked at but ran into similar problems are linked below
How to detect a tap gesture location in SwiftUI?
Swift: Long Press Gesture Recognizer - Detect taps and Long Press This one might be what I'm looking for but I'm not sure how to adapt it to SwiftUI.
.zero
so the Rectangle() is shown in the.zero
CGPoint
– Delvecchio