I want to make it so someone can tap on a Map
and do the following:
Marker
is made where the user tappedMany other apps like Uber or DoorDash have this be possible, but I can't find anything relevant on it online. Articles recommend this method, but if I try making a Marker
, it says this was deprecated. If I try getting tapLocation
the location is not in world coordinates so its not useful.
Map()
.onTapGesture { tapLocation in
print(tapLocation)
}
.mapControls {
MapUserLocationButton()
}
Currently I have some nasty code to use a MKMapView
with a UITapGestureRecognizer
attached to it. The user can tap a point and it gets the tap location converted to the coordinate location from the MKMapView
and set as a MKPointAnnotation()
. The Users location is also set as a state variable so this forces redraws of the USER Location pin constantly (nasty).
import SwiftUI
import MapKit
struct MapInput: UIViewRepresentable {
@Binding var tappedCoordinate: CLLocationCoordinate2D?
@Binding var userLocation: CLLocation?
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
// Add Tap Gesture Recognizer
let tapGestureRecognizer = UITapGestureRecognizer(
target: context.coordinator,
action: #selector(Coordinator.handleTap(gesture:))
)
mapView.addGestureRecognizer(tapGestureRecognizer)
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// Remove existing annotations
uiView.removeAnnotations(uiView.annotations)
// Add new annotation if there's a tapped coordinate
if let coordinate = tappedCoordinate {
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = "POI"
uiView.addAnnotation(annotation)
}
if let user = userLocation {
let annotation = MKPointAnnotation()
annotation.coordinate = user.coordinate
annotation.title = "USER"
uiView.addAnnotation(annotation)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
var parent: MapInput
init(_ parent: MapInput) {
self.parent = parent
}
@objc func handleTap(gesture: UITapGestureRecognizer) {
let mapView = gesture.view as! MKMapView
let touchPoint = gesture.location(in: mapView)
let coordinate = mapView.convert(touchPoint, toCoordinateFrom: mapView)
parent.tappedCoordinate = coordinate
}
}
}
You can convert a CGPoint
to/from a CLLocationCoordinate2D
using a MapReader
, using the convert(_:from:)
method.
But first, you need a structure to store information about the markers on the map, e.g.
struct MarkerInfo: Hashable, Identifiable {
let lat: CLLocationDegrees
let lon: CLLocationDegrees
let id = UUID()
}
Then, store an array of these in a @State
, and append to it in onTapGesture
:
struct ContentView: View {
@State private var markers = [MarkerInfo]()
var body: some View {
MapReader { mapProxy in
Map {
UserAnnotation() // shows the user's location
ForEach(markers) { marker in
Marker("Some Title", coordinate: .init(latitude: marker.lat, longitude: marker.lon))
}
}
.mapControls {
MapUserLocationButton()
}
.onTapGesture { tapLocation in
guard let coordinate = mapProxy.convert(tapLocation, from: .local) else {
print("there is no map!")
return
}
markers.append(.init(lat: coordinate.latitude, lon: coordinate.longitude))
}
}
}
}
Note that the coordinate space that onTapGesture
is .local
by default, hence we use .local
when converting the CGPoint
too.