2020-10-29

SwiftUI: Saving MapKit Annotation Data to Pass To Another View

I am struggling to save MapKit annotation data (e.g. name, address, coordinates, etc.) and pass it back to the view that had the link to the map view. I can select the annotation with didSelect and print to the terminal the data I am looking for, however I can not figure out how to then pass it back. I have been trying to use an ObservedObject but I am getting an error that says: Property 'self.contentData' not initialized at implicitly generated super.init call.

import SwiftUI
import MapKit

//MARK:- COORDINATOR
class Coordinator: NSObject, MKMapViewDelegate {
    
    //@ObservedObject var contentData: ContentData  // << deleted based on answer
    
    var control: MapUIView
    var contentData: ContentData  // << added based on answer
    
    init(_ control: MapUIView) {
        self.control = control
        self.contentData = control.contentData // << added based on answer
    }
    
    func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
        if let annotationView = views.first {
            if let annotation = annotationView.annotation {
                if annotation is MKUserLocation {
                    let region = MKCoordinateRegion(center: annotation.coordinate, latitudinalMeters: 8000, longitudinalMeters: 8000)
                    mapView.setRegion(region, animated: true)
                }
            }
        }
    }
    
    func selectAnnotation(_ annotation: MKAnnotation, animated: Bool) {
        
    }
    
    func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
        if mapView.selectedAnnotations.count > 0 {
            if let selectedPinName = mapView.selectedAnnotations[0].title {
                print("\(selectedPinName!)")
            }
        }
        let region = MKCoordinateRegion(center: view.annotation!.coordinate, span: mapView.region.span)
        mapView.setRegion(region, animated: true)
        contentData.selectedLocationName = (mapView.selectedAnnotations[0].title as? String)!
    }

}

//MARK:- LOCATION MANAGER
class LocationManager: NSObject, ObservableObject {
    private let locationManager = CLLocationManager()
    @Published var location: CLLocation? = nil
    override init() {
        super.init()
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.distanceFilter = kCLHeadingFilterNone
        self.locationManager.requestWhenInUseAuthorization()
        self.locationManager.startUpdatingLocation()
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        print(status)
    }
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else {
            return
        }
        self.location = location
    }
}

//MARK:- LANDMARK ANNOTATION
final class LandmarkAnnotation: NSObject, MKAnnotation {
    let title: String?
    let subtitle: String?
    let coordinate: CLLocationCoordinate2D
    
    init(landmark: Landmark) {
        self.title = landmark.name
        self.subtitle = landmark.title
        self.coordinate = landmark.coordinate
    }
}

//MARK:- LANDMARK
struct Landmark {
    let placemark: MKPlacemark
    var id: UUID {
        return UUID()
    }
    var name: String {
        self.placemark.name ?? ""
    }
    var title: String {
        self.placemark.title ?? ""
    }
    var coordinate: CLLocationCoordinate2D {
        self.placemark.coordinate
    }
}

//MARK:- VIEW
struct MapView: View {
    @ObservedObject var locationManager = LocationManager()
    @State private var landmarks: [Landmark] = [Landmark]()
    @State private var search: String = ""
    @State private var tapped: Bool = false
    @State private var showCancelButton: Bool = false
    
    @ObservedObject var contentData: ContentData

    private func getNearByLandmarks() {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = search
        let search = MKLocalSearch(request: request)
        search.start { (response, error) in
            if let response = response {
                let mapItems = response.mapItems
                self.landmarks = mapItems.map {
                    Landmark(placemark: $0.placemark)
                }
            }
        }
    }
    
    var body: some View {
        ZStack(alignment: .top) {
            MapUIView(contentData: contentData, landmarks: landmarks)
            HStack {
                HStack {
                    Image(systemName: "magnifyingglass")
                    TextField("Search", text: $search, onEditingChanged: { _ in
                        self.showCancelButton = true
                        self.getNearByLandmarks()
                    }).foregroundColor(.primary)
                    Button(action: {
                        self.search = ""
                    }) {
                        Image(systemName: "xmark.circle.fill").opacity(search == "" ? 0 : 1)
                    }
                }
                .padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
                .foregroundColor(.secondary)
                .background(Color(.secondarySystemBackground))
                .cornerRadius(10.0)
            }
            .padding(EdgeInsets(top: 10, leading: 0, bottom: 1, trailing: 0))
            .padding(.horizontal)
        }
    }
}

//MARK:- MAPUIVIEW
struct MapUIView: UIViewRepresentable {
    @ObservedObject var contentData: ContentData
    let landmarks: [Landmark]
    func makeUIView(context: Context) -> MKMapView {
        let map = MKMapView()
        map.showsUserLocation = true
        map.delegate = context.coordinator
        return map
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapUIView>) {
        updateAnnotations(from: uiView)
    }
    func updateAnnotations(from mapView: MKMapView) {
        mapView.removeAnnotations(mapView.annotations)
        let annotations = self.landmarks.map(LandmarkAnnotation.init)
        mapView.addAnnotations(annotations)
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView(contentData: ContentData())
    }
}

Call to the map view:

NavigationLink(destination: MapView(contentData: contentData).edgesIgnoringSafeArea(.bottom), tag: 1, selection: $tag)

Any help would be very much appreciated. Thanks so much in advance.

EDIT ----------------------

Added screenshots of annotation behavior before and after implementing the recommended changes to allow for passing annotation data back to the previous swiftui view with an ObservableObject.

enter image description here



from Recent Questions - Stack Overflow https://ift.tt/37VYoCd
https://ift.tt/34zZ5Pg

No comments:

Post a Comment