swiftuimapkitmkmapviewmapkitannotationuiviewrepresentable

Custom Annotations in SwiftUI (MKMapView)


I have implemented a MKMapView in SwiftUI and I am showing a list of annotations (stops) as well as the user's location. I wanted to add the tap functionality to the "stop pins" but I wasn't able to find anything helpful to achieve this.

The problem with this code is that it changes the view of the user location pin and eventually crashes with the following error.

2021-07-10 18:31:21.434538+0900 Bus Finder[5232:2086940] *** Terminating app due to uncaught exception 'NSGenericException', reason: '<Bus_Finder.Stops: 0x2816c4cc0> must implement title, or view (null) must have a non-nil detailCalloutAccessoryView when canShowCallout is YES on corresponding view <MKAnnotationView: 0x13137cd60; frame = (-20 -20; 40 40); opaque = NO; layer = <CALayer: 0x2832e9e20>>'
*** First throw call stack:
(0x196f2a754 0x1ab9f17a8 0x1a6566410 0x1a65655bc 0x1a656464c 0x1a65641d0 0x1982fd458 0x196ea522c 0x196ea4e28 0x196ea4278 0x196e9e02c 0x196e9d360 0x1ae4db734 0x199918584 0x19991ddf4 0x19ddf3370 0x19ddf32fc 0x19d8ebb6c 0x100eacf54 0x100eacff4 0x196b59cf8)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSGenericException', reason: '<Bus_Finder.Stops: 0x2816c4cc0> must implement title, or view (null) must have a non-nil detailCalloutAccessoryView when canShowCallout is YES on corresponding view <MKAnnotationView: 0x13137cd60; frame = (-20 -20; 40 40); opaque = NO; layer = <CALayer: 0x2832e9e20>>'
terminating with uncaught exception of type NSException

I only want to change the view of the "stops pin" and add the tap functionality. I pass a list of Stops to the MapView on appear. The Stops structure is at the end.

A visual concept of my problem:

When commenting the viewFor annotation function

(When commenting the viewFor annotation function) I want to change the style of the stops pin and add tap functionality to it not the user's location pin.

When using the viewFor annotation function

When I use the viewFor annotation function (the same code as in this question) the user location view changes and then the app crashes.

The MapView file:

// MARK: MapView
struct MapView: UIViewRepresentable {
    
    // MARK: Variables
    @Binding var stops: [Stops]
    @Binding var centerCoordinate: MKCoordinateRegion
    @Binding var action: Action
    
    // MARK: Action Lists
    enum Action {
        case idle
        case reset(coordinate: MKCoordinateRegion)
        case changeType(mapType: MKMapType)
    }
    
    // MARK: First Time Only
    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView()
        mapView.delegate = context.coordinator
        mapView.showsUserLocation = true
        mapView.userTrackingMode = .follow
        mapView.isUserInteractionEnabled = true
        mapView.centerCoordinate = self.centerCoordinate.center
        mapView.setRegion(self.centerCoordinate, animated: true)
        return mapView
    }
    
    // MARK: Updating UI
    func updateUIView(_ view: MKMapView, context: Context) {
        switch action {
        case .idle:
            break
        case .reset(let newCoordinate):
            view.delegate = nil
            
            DispatchQueue.main.async {
                self.centerCoordinate.center = newCoordinate.center
                self.action = .idle
                view.setRegion(self.centerCoordinate, animated: true)
                view.delegate = context.coordinator
            }
        case .changeType(let mapType):
            view.mapType = mapType
        }
        view.addAnnotations(stops)
    }
    
    // MARK: Setting Coordinator
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, MKMapViewDelegate {
        var parent: MapView
        
        init(_ parent: MapView) {
            self.parent = parent
        }
        
        func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
            parent.centerCoordinate.center = mapView.centerCoordinate
            
        }
        
        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "TESTING NOTE")
            annotationView.canShowCallout = true
            annotationView.image = UIImage(systemName: "location.circle")?.withTintColor(.systemGreen, renderingMode: .alwaysOriginal)
            let size = CGSize(width: 40, height: 40)
            annotationView.image = UIGraphicsImageRenderer(size:size).image {
                _ in annotationView.image!.draw(in:CGRect(origin:.zero, size:size))
            }
            return annotationView
        }
        
    }
}

Stops structure file:

// MARK: StopPinPoint
final class Stops: NSObject, Codable, Identifiable, MKAnnotation {
    
    var id: String?
    var name: BusStopName
    var images: [String]?
    var landMarks: [String]?
    var coordinate: CLLocationCoordinate2D
    var prevNexStop: [String]?
    
    init(id: String?, name: BusStopName, images: [String]?, landMarks: [String]?, coordinates: CLLocationCoordinate2D, prevNextStop: [String]?) {
        self.id = id
        self.name = name
        self.coordinate = coordinates
        self.images = images
        self.landMarks = landMarks
        self.prevNexStop = prevNextStop
    }
    
    var location: CLLocation {
        return CLLocation(latitude: self.coordinate.latitude, longitude: self.coordinate.longitude)
    }
    
    func distance(to location: CLLocation) -> CLLocationDistance {
        return location.distance(from: self.location)
    }
}

I would appreciate it a lot if someone could help me! I have been working on this problem for weeks now!


Solution

  • So basically after a bit more searching, I found out the answer.

    In order to not change the user's location pin, I have to check the type of annotation and if the type is MKUserLocation I should return nil.

    Following that the reason for the crash was that I had to make the Stops structure confirm to MKPointAnnotation and remove or override the coordinate variable then when I am making a list of Stops I can simply define the title, subtitle and coordinates.