SwiftUI / MapKit Annotation Selection and Navigation

Viewed 2528

I currently have a SwiftUI view that displays a map full of pins. The map has a delegate which can determine when a pin is selected and get the custom object associated with that pin.

The problem is that once the delegate determines that the pin has been selected, I need my SwiftUI view to navigate/push to a detail page with the custom object that the delegate retrieved.

The best comparison I can think of is trying to treat the selected annotation as a navigation link button for the main view.

How do I communicate from the map delegate to the SwiftUI view?


import SwiftUI
import MapKit

/// The SwiftUI view I am referring to
struct ContentView: View {
    var body: some View {
        ZStack {
            MapView()
        }
        .edgesIgnoringSafeArea(.all)
    }
}


/// The custom object I am referring to
class LandmarkAnnotation: NSObject, MKAnnotation {
    let title: String?
    let subtitle: String?
    let coordinate: CLLocationCoordinate2D
    init(title: String?,
         subtitle: String?,
         coordinate: CLLocationCoordinate2D) {
        self.title = title
        self.subtitle = subtitle
        self.coordinate = coordinate
    }
}

/// The delegate I am referring to
class MapViewCoordinator: NSObject, MKMapViewDelegate {
    var mapViewController: MapView
    
    init(_ control: MapView) {
        self.mapViewController = control
    }
    
    func mapView(_ mapView: MKMapView, viewFor
                    annotation: MKAnnotation) -> MKAnnotationView?{
        ...
        return annotationView
    }
    
    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        let circle = MKCircleRenderer(overlay: overlay)
        ...
        return circle
    }
    
    /// This is where the delegate gets the object for the selected annotation
    func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
        if let v = view.annotation as? LandmarkAnnotation {
            print(v.coordinate)
        }
    }
}


struct MapView: UIViewRepresentable {
    
    var markers: [CLLocationCoordinate2D] = [CLLocationCoordinate2D(
                                                latitude: 34.055404, longitude: -118.249278),CLLocationCoordinate2D(
                                                    latitude: 34.054097, longitude: -118.249664), CLLocationCoordinate2D(latitude: 34.053786, longitude: -118.247636)]
    var convertedMarkers: [LandmarkAnnotation] = []
    
    init() {
        convertedMarkers = cordToMark(locations: self.markers)
    }
    
    func makeUIView(context: Context) -> MKMapView{
        MKMapView(frame: .zero)
    }
    
    func cordToMark(locations: [CLLocationCoordinate2D]) -> [LandmarkAnnotation] {
        var marks: [LandmarkAnnotation] = []
        for cord in locations {
            let mark = LandmarkAnnotation(title: "Test", subtitle: "Sub", coordinate: cord)
            marks.append(mark)
        }
        return marks
    }
    
    func makeCoordinator() -> MapViewCoordinator{
        MapViewCoordinator(self)
    }
    
    func updateUIView(_ view: MKMapView, context: Context){
        let coordinate = CLLocationCoordinate2D(
            latitude: 34.0537767, longitude: -118.248)
        let mapCamera = MKMapCamera()
        mapCamera.centerCoordinate = coordinate
        mapCamera.pitch = 10
        mapCamera.altitude = 3000
        view.camera = mapCamera
        view.mapType = .mutedStandard
        view.delegate = context.coordinator
        view.addAnnotations(self.convertedMarkers)
        let radiusCircle = MKCircle(center: CLLocationCoordinate2D(
                                        latitude: 34.0537767, longitude: -118.248), radius: 300 as CLLocationDistance)
        view.addOverlay(radiusCircle)
        let locationCircle = MKCircle(center: CLLocationCoordinate2D(
                                        latitude: 34.0537767, longitude: -118.248), radius: 3 as CLLocationDistance)
        view.addOverlay(locationCircle)
    }
    
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environment(\.colorScheme, .light)
    }
}


Here is an image of the map that is displayed in the simulator

enter image description here

1 Answers

Here is an idea (scratch)

  1. add a callback to representable
struct MapView: UIViewRepresentable {
    var didSelect: (LandmarkAnnotation) -> ()  // callback
  1. so in ContentView
ZStack {
    MapView() { annotation in
       // store/pass annotation somewhere, and
       // activate navigation link here, eg. via isActive or selection
    }
}
  1. activate callback in delegate
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
    if let v = view.annotation as? LandmarkAnnotation {
        print(v.coordinate)

       self.mapViewController.didSelect(v)    // << here !!
    }
}
Related