Yohei Onishi
Yohei Onishi

Reputation: 13

MKMarkerAnnotationView Error, Terminating app due to uncaught exception

enter image description here

When annotation is displayed on the map, it can be built and executed, but an error occurs during execution and it stops. MKMarkerAnnotationView displays clustering and glyph image. There are about 100 annotations, which are acquired from Firestore and stored in an array when View is loaded. The error occurred when the map was enlarged or moved. Swift version: Swift5 I also checked the outlet connection, but it looks like there is no problem.

Can't find a solution and can you tell me what's wrong? I wrote about the setting method of Map, etc., but I would appreciate if you could teach me if it is fundamentally wrong.

Error [XXX.CustomPinAnnotation memberAnnotations]: unrecognized selector sent to instance 0x2835e16c0

CustomPinAnnotation.swift


import UIKit
import MapKit

class CustomPinAnnotation: NSObject, MKAnnotation {

    let clusteringIdentifier : String
    let title: String?
    let subtitle: String?
    let coordinate: CLLocationCoordinate2D
    //let glyphText: String
    let glyphImage: UIImage
    let glyphTintColor: UIColor
    let markerTintColor: UIColor
    let objectid: Int

    init(_ clusteringIdentifier: String, title: String, subtitle: String, coordinate: CLLocationCoordinate2D, glyphImage: UIImage, glyphTintColor: UIColor, markerTintColor: UIColor, objectid: Int) {
        self.clusteringIdentifier = clusteringIdentifier
        self.title = title
        self.subtitle = subtitle
        self.coordinate = coordinate
//        self.glyphText = glyphText
        self.glyphImage = glyphImage
        self.glyphTintColor = glyphTintColor
        self.markerTintColor = markerTintColor
        self.objectid = objectid
    }
}

ViewController.swift

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {

    var locationManager = CLLocationManager()
    var userCoordinate = CLLocationCoordinate2D()

    //object array
    var objectAll = [objectData]()        

    //Map view
    @IBOutlet weak var mapView: MKMapView!

    func displayAllMountains() {
        for mountain in objectAll {
            let pinImage = UIImage.init(named: "XXXXX")!
            let subtitletext = String(object.height) + "m"
            let annotation = CustomPinAnnotation("clusterid", title:object.name, subtitle: subtitletext, coordinate: object.geopoint, glyphImage: pinImage, glyphTintColor: .white, markerTintColor: .darkGray, objectid: object.objectid)                        
            self.mapView.addAnnotation(annotation)
        }
    }

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {   
        if annotation === mapView.userLocation {
            return nil
        } else {
            let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier, for: annotation)

            guard let markerAnnotationView = annotationView as? MKMarkerAnnotationView,
                let annotation = annotation as? CustomPinAnnotation else {
                     return annotationView
            }

            markerAnnotationView.clusteringIdentifier = annotation.clusteringIdentifier
//            markerAnnotationView.glyphText = annotation.glyphText
            markerAnnotationView.glyphImage = annotation.glyphImage
            markerAnnotationView.glyphTintColor = annotation.glyphTintColor
            markerAnnotationView.markerTintColor = annotation.markerTintColor
            return markerAnnotationView
        }
    }
}

Upvotes: 1

Views: 503

Answers (1)

Rob
Rob

Reputation: 437372

You say it’s reporting:

Error [XXX.CustomPinAnnotation memberAnnotations]: unrecognized selector sent to instance 0x2835e16c0

The memberAnnotations is a MKClusterAnnotation method. But it’s trying to call that method on your CustomPinAnnotation object. Somewhere, it’s trying to use a CustomPinAnnotation in a context where it was expecting a MKClusterAnnotation.

I’ve had troubles manifesting your problem, and I’m not sure you’ve shared enough for us to reproduce it. But I can easily see how it get be confused. There are three annotation types, not only MKUserLocation and CustomPinAnnotation, but also MKClusterAnnotation. Your mapView(_:viewFor:) only contemplates the existence of the first two types. You can make it handle MKClusterAnnotation, too, if you want:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    if annotation is MKUserLocation {
        return nil
    } else if annotation is MKClusterAnnotation {
        return mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier, for: annotation)
    }

    let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier, for: annotation)

    guard
        let markerAnnotationView = annotationView as? MKMarkerAnnotationView,
        let annotation = annotation as? CustomPinAnnotation
    else {
        return annotationView
    }

    markerAnnotationView.clusteringIdentifier = annotation.clusteringIdentifier
    markerAnnotationView.displayPriority = .required
    // markerAnnotationView.glyphText = annotation.glyphText
    markerAnnotationView.glyphImage = annotation.glyphImage
    markerAnnotationView.glyphTintColor = annotation.glyphTintColor
    markerAnnotationView.markerTintColor = annotation.markerTintColor
    return markerAnnotationView
}

Personally, though, I'd suggest removing mapView(_:viewFor:) entirely, and instead define your own annotation view class that does this configuration:

class CustomAnnotationView: MKMarkerAnnotationView {
    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
        update(for: annotation)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override var annotation: MKAnnotation? { didSet { update(for: annotation) } }
    
    func update(for annotation: MKAnnotation?) {
        displayPriority = .required

        guard let annotation = annotation as? CustomPinAnnotation else { return }
        
        clusteringIdentifier = annotation.clusteringIdentifier
        // markerAnnotationView.glyphText = annotation.glyphText
        glyphImage = annotation.glyphImage
        glyphTintColor = annotation.glyphTintColor
        markerTintColor = annotation.markerTintColor
    }
}

And, of course, make sure to register it (and your cluster annotation view, too):

mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
mapView.register(MKMarkerAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)

But the key is that the default implementation of mapView(_:viewFor:) handles all three types of annotation views automatically. And this way, we take gritty “how do I render this annotation view” code out of the view controller (or whatever the MKMapViewDelegate was) and into the annotation view class, where it belongs.

Upvotes: 1

Related Questions