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) I want to change the style of the stops pin and add tap functionality to it not the user's location pin.
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!
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.