0
votes

I am showing 3 distinct annotations in a map. To achive this I have a enum as a class variable which indicates the current value of the image name to be set in the MKAnnotationView property. I have subclassed MKAnnotationView to sore a class variable to get the image name in case of annotation reuse.

The problem is that when I drag the map leaving the annotations out of view and when I drag it again to see the annotations, these have their images exchanged.

My enum and custom MKAnnotationView class:

    enum AnnotationIcon: String {
        case taxi
        case takingIcon
        case destinyIcon
    }

final class MyMKAnnotationView: MKAnnotationView {
    var iconType: String = ""
}

And this is my viewFor annotation function:

 func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        
        guard !(annotation is MKUserLocation) else {
            return nil
        }
        
        let identifier = "Custom annotation"
        
        var annotationView: MyMKAnnotationView?
        
        guard let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MyMKAnnotationView else {
            let av = MyMKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
            annotationView = av
            annotationView?.annotation = annotation
            annotationView?.canShowCallout = true
            annotationView?.translatesAutoresizingMaskIntoConstraints = false
            annotationView?.widthAnchor.constraint(equalToConstant: 35).isActive = true
            annotationView?.heightAnchor.constraint(equalToConstant: 35).isActive = true
            annotationView?.iconType = annotationIcon.rawValue //AnnotationIcon enum class variable
            annotationView?.image = UIImage(named: annotationView?.iconType ?? "")
            
            return av
        }
        annotationView = dequeuedAnnotationView
        annotationView?.image = UIImage(named: annotationView?.iconType ?? "")
        
        return annotationView
    }

Images that explain the problem:

Before the draggin:

enter image description here

After the draggin:

enter image description here

What is the way for each annotation to retrieve the correct image in case of reuse?

Thank you.

3
Checking the memmory address of the MKAnnotationViews, they are being relocated over the map. Not exchanging their images. - Andoni Da Silva

3 Answers

1
votes

Before reusing the MyMKAnnotationView you've to empty the already set image in the prepareForReuse method.

final class MyMKAnnotationView: MKAnnotationView {
    var iconType: String = ""
    //...
    override func prepareForReuse() {
        super.prepareForReuse()
        image = nil // or set a default placeholder image instead
    }
}

Update: As suspected the iconType is not getting set before you're trying to set the image of MyMKAnnotationView. Either you need to set the iconType before setting the image, like this:

annotationView?.iconType = AnnotationIcon.taxi.rawValue
annotationView?.image = UIImage(named: annotationView?.iconType ?? "")

You can improve this a lot by being the image returning logic to the AnnotationIcon.

enum AnnotationIcon: String {
    //...
    var annotationImage: UIImage? { UIImage(named: rawValue) }
}

Then change the MyMKAnnotationView as follows:

final class MyMKAnnotationView: MKAnnotationView {
    var iconType = AnnotationIcon.taxi {
        didSet {
            image = iconType.annotationImage // image is set whenever `iconType` is set
        }
    }
    //...
}

Then in viewForAnnotation:

var iconTypes = [AnnotationIcon]() // should be equal to the number of annotation

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    //...
    dequeuedAnnotationView.annotation = annotation
    dequeuedAnnotationView.iconType = iconType
}
0
votes

Your problem is that you only set the iconType property when you create the annotation view. When an annotation view is reused, you set the image based on that property rather the current annotation.

Really, there is no need for the iconType property. You should just always use an icon value from your annotation. The annotation is the data model.

You also don't set the view's annotation property correctly in the case of reuse.

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        
        guard let myAnnotation = annotation as? MyAnnotation else {
            return nil
        }
        
        let identifier = "Custom annotation"
           
        var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MyMKAnnotationView
        if annotationView == nil {
            annotationView = MyMKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
            annotationView?.canShowCallout = true
            annotationView?.translatesAutoresizingMaskIntoConstraints = false
            annotationView?.widthAnchor.constraint(equalToConstant: 35).isActive = true
            annotationView?.heightAnchor.constraint(equalToConstant: 35).isActive = true
        }
        annotationView?.annotation = MyAnnotation
        annotationView?.image = UIImage(named: MyAnnotation.icon.rawValue)
        
        return annotationView
    }
0
votes

I have solved this issue subclassing the MKPointAnnotation to know what type of Annotation I am reusing:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        
        guard !(annotation is MKUserLocation) else {
            return nil
        }
        var annotationView: MKAnnotationView?
        switch annotation {
        case is MyCustomTaxiAnnotation:
            annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationTaxiID, for: annotation)
        case is MyCustomOriginAnnotation:
            annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationOriginID, for: annotation)
        case is MyCustomDestinyAnnotation:
            annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationDestinyID, for: annotation)
        default:
            annotationView = nil
        }
        if annotationView == nil {
            switch annotationIcon {
            case .destinyIcon:
                annotationView = MyDestinyView(annotation: annotation, reuseIdentifier: annotationDestinyID)
                annotationView?.annotation = MyCustomDestinyAnnotation()
            case .takingIcon:
                annotationView = MyOriginView(annotation: annotation, reuseIdentifier: annotationOriginID)
                annotationView?.annotation = MyCustomOriginAnnotation()
            case .taxi:
                annotationView = MyTaxiView(annotation: annotation, reuseIdentifier: annotationTaxiID)
                annotationView?.annotation = MyCustomTaxiAnnotation()
            }
        } else {
            annotationView?.annotation = annotation
        }
        return annotationView
    }

For example, the Taxi annotation is a subclass of MKPointAnnotation:

let annotation = MyCustomTaxiAnnotation()
final class MyCustomTaxiAnnotation: MKPointAnnotation {
///...
}

So, taking in account that, I am able to reuse a custom annotation view properly. I have also register the custom MKAnnotationView:

map.register(MyOriginView.self, forAnnotationViewWithReuseIdentifier: annotationOriginID)
        map.register(MyDestinyView.self, forAnnotationViewWithReuseIdentifier: annotationDestinyID)
        map.register(MyTaxiView.self, forAnnotationViewWithReuseIdentifier: annotationTaxiID)