How do you let a user add a pin to a map and get the coordinates in SwiftUI?
Asked Answered
D

3

8

I have a map in a view that is centered on Chicago. I want the user to be able to place a pin/annotation on the map and then retrieve those coordinates. The map loads Chicago fine but I can't get the annotation code to work.

I can't seem to find an answer for SwiftUI, specifically. Only Swift and Storyboards. I feel like I have 99% of the code but the pieces aren't in the right spots. I included a screen shot of where the errors are. Thanks for your help.

import SwiftUI
import MapKit

struct EntireMapView: UIViewRepresentable {

    func updateUIView(_ mapView: MKMapView, context: Context) {

        mapView.delegate = context.coordinator

        let longPressGesture = UILongPressGestureRecognizer(target: mapView, action: #selector(EntireMapViewCoordinator.addAnnotation(gesture:)))
        mapView.addGestureRecognizer(longPressGesture)

        let span = MKCoordinateSpan(latitudeDelta: 0.3, longitudeDelta: 0.3)

        var chicagoCoordinate = CLLocationCoordinate2D()
        chicagoCoordinate.latitude = 41.878113
        chicagoCoordinate.longitude = -87.629799

        let region = MKCoordinateRegion(center: chicagoCoordinate, span: span)

        mapView.setRegion(region, animated: true)

    }

    func makeUIView(context: Context) -> MKMapView {

        let mapView = MKMapView(frame: .zero)
        mapView.delegate = context.coordinator
        return mapView

    }

    func makeCoordinator() -> EntireMapViewCoordinator {
        return EntireMapViewCoordinator(self)
    }

    class EntireMapViewCoordinator: NSObject, MKMapViewDelegate {

        var entireMapViewController: EntireMapView

        init(_ control: EntireMapView) {
          self.entireMapViewController = control
        }

        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            return (annotation as? MKAnnotationView)!
        }

        @objc func addAnnotation(gesture: UILongPressGestureRecognizer) {

            if gesture.state == .ended {

                let point = gesture.location(in: self.mapView) **<--- ERROR HERE**
                let coordinate = mapView.convert(point, toCoordinateFrom: mapView)

                var annotation = MKPointAnnotation()
                annotation.coordinate = coordinate

                self.mapView.addAnnotation(annotation) **<--- ERROR HERE**
            }
        }
    }
}

struct EntireMapView_Previews: PreviewProvider {
    static var previews: some View {
        EntireMapView()
    }
}

Here's a screen shot of the errors

Dodona answered 4/12, 2019 at 18:26 Comment(0)
S
5

Here is a complete code for the answer. The credit goes mostly to @yeezy , because obtaining view with this line: let mapView = gestureRecognizer.view as? MKMApView was the main mistake in the question.

  struct EntireMapView: UIViewRepresentable {

        func updateUIView(_ mapView: MKMapView, context: Context) {

            let span = MKCoordinateSpan(latitudeDelta: 0.3, longitudeDelta: 0.3)
            var chicagoCoordinate = CLLocationCoordinate2D()
            chicagoCoordinate.latitude = 41.878113
            chicagoCoordinate.longitude = -87.629799
            let region = MKCoordinateRegion(center: chicagoCoordinate, span: span)
            mapView.setRegion(region, animated: true)

        }

        func makeUIView(context: Context) -> MKMapView {

            let myMap = MKMapView(frame: .zero)
            let longPress = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(EntireMapViewCoordinator.addAnnotation(gesture:)))
            longPress.minimumPressDuration = 1
            myMap.addGestureRecognizer(longPress)
            myMap.delegate = context.coordinator
            return myMap

        }

    func makeCoordinator() -> EntireMapViewCoordinator {
        return EntireMapViewCoordinator(self)
    }

    class EntireMapViewCoordinator: NSObject, MKMapViewDelegate {

        var entireMapViewController: EntireMapView

        init(_ control: EntireMapView) {
          self.entireMapViewController = control
        }


        @objc func addAnnotation(gesture: UIGestureRecognizer) {

            if gesture.state == .ended {

                if let mapView = gesture.view as? MKMapView {
                let point = gesture.location(in: mapView)
                let coordinate = mapView.convert(point, toCoordinateFrom: mapView)
                let annotation = MKPointAnnotation()
                annotation.coordinate = coordinate
                mapView.addAnnotation(annotation)
                }
            }
        }
    }
}
Scut answered 23/3, 2020 at 3:21 Comment(0)
O
1

i don't know if you are still looking for an answer. But here is one. I struggled with this problem a lot and i don't know if my answer is correct but it works for me so here it is:

@objc func addAnnotation(gesture: UITapGestureRecognizer){
   if let mapView = gestureRecognizer.view as? MKMApView{
      let point = gestureRecognizer.location(in: mapView) 
      let coordinate = mapView.convert(point, toCoordinateFrom: mapView) 
      let annotation = MKPointAnnotation() 
      annotation.coordinate = coordinate 
      DispatchQueue.main.async{
          mapView.addAnnotation(annotation)
        }
    }
}

as you can see i am not using an UILongPressGestureRecognizer but rather an UITapGestureRecognize and i took your idea of using the gestureRecognizers view.

Disclaimer: I don't know if this is the best way to do it! I just started swift/swiftUI programming half a year ago :) and this is my first answer.

For further implementation: You should prolly just collect all the annotations in an array and update the mapView from within the updateUIView method. I dont know if i am correct when using the DispatchQueue.main.async here. But whenever i come across changes of the Ui i think you should do it on the main thread

Screenshot with explanation

Ocasio answered 5/3, 2020 at 12:45 Comment(0)
F
0

I would start by taking mapView.delegate = context.coordinator out of updateUIView. This should be done only once, in makeUIView (which you're already doing).

Faint answered 27/12, 2019 at 7:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.