Get coordinates in the onLongPressGesture on the Map

I've asked this question at SO, but there are no replies there, so I decided to give it a try here.

Basically, I want to be able to capture the coordinates of a long press on the Map component (not the geo-coordinates - that's a separate task - just local to the app coordinates).

The problem is that the .onLongPressGesture doesn't provide the coordinates of where the long press happened. .onTapGesture does - but I don't want to register taps, I want to register long presses.

One of the work-arounds suggested at SO was to leverage the DragGesture, and it kinda works for other views, but when applied to a Map, it breaks the default dragging functionality of the Map component (so that it becomes impossible to scroll the map region). Also this work-around provides the coordinates only when the finger is released (at the end of the .onLongPressGesture), but I want to capture the long-press location before the finger was released (but after some time of holding, for example 1 second).

Answered by coffeeshot in 758412022

I managed to achieve it by creating a custom UIViewRepresentable with MKMapView inside. Then using UILongPressGestureRecognizer and implementing @objc handleLongPress method. Maybe not so elegant solution, but does exactly what's needed. I really hope that map gets some improvements in iOS 17.

I was able to accomplish using the swiftui-introspect library. Essentially falling back to MKMapKit functionality for this particular use case. I am hoping that the new MapReader and MapProxy capability in IOS 17 will help avoid having to use the introspect workaround but I cannot figure out how to use them.

Here is the code that uses introspect to get the coordinates of an annotation as it is being dragged on a map in SwiftUI.


import SwiftUI
import MapKit
import SwiftUIIntrospect

struct ContentView: View {
    @State var pinLocation : CLLocationCoordinate2D = .denver
    @GestureState var dragAmount = CGSize.zero
    @State var mkMapView: MKMapView = .init()
    @State var offset: CGSize = .zero
    
    @State private var cameraProsition: MapCameraPosition = .camera(
        MapCamera(
            centerCoordinate: .denver,
            distance: 3729,
            heading: 92,
            pitch: 70
        )
    )
    
    func longPressDrag() -> some Gesture {
        LongPressGesture(minimumDuration: 0.25)
            .sequenced(before: DragGesture(coordinateSpace: .local)
            .onEnded { value in
                let convertedCoordinate = mkMapView.convert(value.location, toCoordinateFrom: mkMapView)
                pinLocation = convertedCoordinate
                offset = .zero
                print("New Coordinate: \(pinLocation)")
            }
            .updating($dragAmount) { value, state, transaction in
                state = value.translation

                offset = value.translation
            })
    }
        
    var body: some View {
        VStack{
            Map(
                position: $cameraProsition,
                interactionModes: []
            )
            {
                Annotation("Center", coordinate: pinLocation) {
                    RoundedRectangle(cornerRadius: 10)
                        .foregroundColor(.pink)
                        .frame(width: 100, height: 100)
                        .offset(dragAmount)
                }
            }
            .gesture(longPressDrag())
            .introspect(.map, on: .iOS(.v17)) { map in
                mkMapView = map
            }
            .mapStyle(.standard(elevation: .automatic))
        }
    }
}

#Preview {
    ContentView()
}
Accepted Answer

I managed to achieve it by creating a custom UIViewRepresentable with MKMapView inside. Then using UILongPressGestureRecognizer and implementing @objc handleLongPress method. Maybe not so elegant solution, but does exactly what's needed. I really hope that map gets some improvements in iOS 17.

Another alternative is to use the LongPressGesture in the context of a GeometryReader:

import SwiftUI

GeometryReader { proxy in
    HStack { Text("Press Me") }
       .onLongPress {
           let longBounds = proxy.frame(in: .global)
           let longPoint = CGPoint(x: longBounds.midX, y: longBounds.midY)
           // invoke a call, send the point to a publisher, or something
    }
}

Get coordinates in the onLongPressGesture on the Map
 
 
Q