Reputation: 15217
Setup:
I am converting an app from UIKit to SwiftUI.
The app uses now instead of an MKMapView
a SwiftUI Map
.
Unfortunately, not all features of an MKMapView
are already provided by Apple for Map
, e.g. dragging a map annotation and getting the coordinate of the drag end point.
Anyway, I am trying thus to implement a similar function in SwiftUI. This is the development body of my annotation view:
var body: some View {
GeometryReader { geo in // 1
let frameInGlobal = geo.frame(in: .global)
Image("PinRed")
.background(Color.green) // 2
.offset(CGSize(width: dragAmount.width, height: dragAmount.height - liftOff)) // 3
.defersSystemGestures(on: .all) // 4
.onTapGesture {
print("Global center: \(frameInGlobal.midX) x \(frameInGlobal.midY)")
}
.gesture(drag(frameInGlobal: frameInGlobal)) // 5
.simultaneousGesture( // 6
LongPressGesture(minimumDuration: 0.2)
.onEnded { _ in // 7
liftOff = 40.0
}
)
}
.frame(width: 30, height: 30)
.background(Color.red) // 2
}
<1> GeometryReader
is used to get the screen coordinates during dragging.
<2> The background colors are there so that it can easily be identified what happens.
<3> The pin image is offset by the drag amount plus a vertical shift so that the pin is visible above the finger.
<4> The system gestures, e.g. zoom in after double tap and long press to drag the map, are deferred so that the custom gestures come through.
<5> The custom drag gesture essentially changes a @GestureState var dragAmount = CGSize.zero
so that the pin image follows the finger.
<6> A simultaneousGesture
is required so that the custom tap gesture as well as the custom long press gesture can be used.
<7> That the pin is lift off, indicates that dragging can start.
Problem:
If the liftOff
value is 0.0
, I can tap the annotation, which prints out its position, and I can long press it and drag it around. After dragging stopped, it still accepts further taps and long presses.
However, when the liftOff
value is e.g. 40.0
, the annotation can only be tapped as long as it is not dragged, and when it has been dragged the 1st time, it cannot be dragged again. This means the custom gestures do no longer come through. Instead, only the system gestures work.
Here is an example of the situation after the 1st drag (green is the image background, red the GeometryReader
background).
Question:
Why are the custom gestures only accepted when the pin is not lift off?
And how to do it right?
Edit:
I just found out that my custom gestures work as long as Image
(green square) and GeometryReader
(red square) overlap, and the tap point, long press point and start point of a drag are within the overlap area.
Upvotes: 0
Views: 341
Reputation: 15217
Problem solved. It has nothing to do with SwiftUI:
If in the code example above liftOff
is 0
, the green View
(Image
) and the red View
(GeometryReader
) have always the same position on the screen. If the green View
is tapped, the hit position is forwarded to the custom gestures. Since the red View
has the same position on the screen, the red View
handles the hit.
If however liftOff
is larger than the frame
size of the green and red View
, both do not overlap on the screen. If now the green View
is tapped, the hit position is forwarded to the red View
that does not overlap the green View
, i.e. the red View
is not hit, and the hit is forwarded to the next View
behind the green View
, which is the Map
itself. Thus the custom gestures are not triggered, but the system gestures.
In the code above, this can easily be checked by adding line 3 additionally as last view modifier of the GeometryReader
.
Upvotes: 0