How to convert tap gesture to coordinate in a SwiftUI Map view?
Asked Answered
T

2

20

I'm trying to add a MapAnnotation item to my Map() View from MapKit.

Everything works fine if you add coordinates manually in a TextField, but I can't find any way to add it by tapping on the map.

I've read a lot on the internet but I've found nothing about onTap() map event handling.

This is what my map view looks like so far:

struct MyMap : View {
    
    @State private var myRegion:MKCoordinateRegion = {
        var newRegion = MKCoordinateRegion()
        newRegion.center.latitude = 53.7576508
        newRegion.center.longitude = -11.1811597
        newRegion.span.latitudeDelta = 0.1
        newRegion.span.longitudeDelta = 0.1
        
        return newRegion
    }()
    
    
    @State private var annotationItems : [Annotation] = [
        Annotation(name: "Pin1", description: "myDescription", coordinate: CLLocationCoordinate2D(latitude: 123.7576508, longitude: -7.2373215))
    ]
    
    var body: some View {
        
        ZStack {
            Map(coordinateRegion: self.$myRegion, interactionModes: .all, annotationItems: annotationItems){ item in
                MapAnnotation(coordinate: item.coordinate){
                    PinView(pin: item)
                }
                
            }
            .edgesIgnoringSafeArea(.all)
        }
    }
}

Is there any way to get the coordinate of where user tapped on the map?

Tsang answered 30/4, 2021 at 14:26 Comment(4)
I deleted my answer because the OP wants the answer to be exclusively in SwiftUI. That said, #56514442 may help. Furthermore, because UIKit and SwiftUI can work together side by side in the same project, you can take advantage of convert(touch, toCoordinateFrom: mapView) in the tap gesture handler in UIKit if it's too much trouble getting it in SwiftUI.Spivey
Possibly same issue as #63111173Ernesternesta
@trndjc Too bad you deleted your answer. Even if OP wanted it in pure SwiftUI, your answer still would have been very useful for someone else.Unbuild
@Unbuild I undeleted it.Spivey
G
14

iOS 17 now has a MapReader for the SwiftUI map. You wrap the Map like this:

struct ContentView: View {
    var body: some View {
        MapReader { reader in
            Map()
            .onTapGesture(perform: { screenCoord in
                let pinLocation = reader.convert(screenCoord, from: .local)
                print(pinLocation)
            })
        }
    }
}

and you can extract the lat/long of the touch.

Glooming answered 7/8, 2023 at 19:33 Comment(6)
Thanks James, worked like a charm. This should be the accepted answer for iOS >= 17.Turbine
Indeed, works like a charm for single tap. Thank you! I noticed that on double tap, single tap is also executed. Is there any chance to block the single tap in this case? I noticed that I can't even listen for that double tap as .onTapGesture(count: 2) is not triggered for double taps on the map.Destitution
@Destitution That's a good question. I'll play with this and see if I can figure something out.Glooming
@Destitution It looks like the double-tap is already used by the map to zoom in. What about a long press? That is triggered distinctly from the tap (ie, the long press doesn't also trigger the tap). Could that work for your use case? Just add it like this: .onLongPressGesture { print("Long press") }Glooming
@JamesToomey unfortunately the long tap is not an option for me. I did manage to do it with a MKMapView in a UIViewRepresentable but I don't like that option 100%. Too bad swiftUi version is not configurable enough.Destitution
@Destitution Ahh okay. That's crummy! I'm going back and forth between using the SwiftUI native map and the UIKit one also. The SwiftUI one still doesn't seem as full-featured yet.Glooming
S
2

What you can do is add a tap gesture recognizer to the map and then convert the touch point to coordinates in the recognizer's handler:

@objc func tapMapAction(recognizer: UITapGestureRecognizer) {
    let touch = recognizer.location(in: mapView)
    let coord = mapView.convert(touch, toCoordinateFrom: mapView)

    let annot = MKPointAnnotation()
    annot.coordinate = coord
    mapView.addAnnotation(annot)
}

At the request of someone, I've undeleted this answer; also given that there is no accepted answer in SwiftUI and that UIKit is still the dominant framework for production-apps today.

Spivey answered 30/4, 2021 at 15:32 Comment(6)
Yeah but SwiftUI not UIKitHyperkeratosis
The SwiftUI solution, as of now in its infancy, isn't very attractive (#56514442) so just use UIKit. There's nothing preventing anyone from mixing UIKit and SwiftUI in the same app.Spivey
@bxod Is this answer compatible even for Map() or just MKMapView()?Tsang
@AlessioRaddi if Map() is just an instance of MKMapView() then yes, which I assume it is.Spivey
@bxod Nope, it’s a View introduced in SwiftUI 2.0 I guess. developer.apple.com/documentation/mapkit/mapTsang
Now I understand, you are dealing exclusively in SwiftUI. Map is a SwiftUI component within MapKit which uses the same map as UIKit. 'Map` is simply a view with an embedded MapKit map. Your question should probably exclude the Swift tag and be slightly reworded to focus exclusively on SwiftUI. I don't know how this would work in SwiftUI exclusively because this is all very new in iOS but you can interoperate UIKit and SwiftUI which means that if you can't find an answer that is exclusively in SwiftUI then my answer would likely be the best alternative so I'll leave it up for that.Spivey

© 2022 - 2024 — McMap. All rights reserved.