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.
Here is fixed part
class Coordinator: NSObject, MKMapViewDelegate {
var contentData: ContentData // << here !!
var control: MapUIView
init(_ control: MapUIView) {
self.control = control
self.contentData = control.contentData // << here !!
}
// ... other code
}
Note: @ObservedObject
is designed to be used in SwiftUI view and not needed in class