Search code examples
swiftannotationsmapkitxibnib

How to make a custom MKAnnotationView with XIB


I want to have a custom MKAnnotationView. I've created a xib file in IB and set its class to MyAnnotationView.

    class MyAnnotationView: MKAnnotationView {

    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @IBOutlet weak var textLabel: UILabel!
    @IBOutlet weak var busIcon: UIImageView!

}

Here's how the xib looks like - it has a textLabel and a busIcon linked:

enter image description here

I'm using the viewFor annotation delegate method to create views for all annotations:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {     

        // Don't want to show a custom image if the annotation is the user's location.
        if (annotation is MKUserLocation) {
            return nil
        } else {

            let annotationIdentifier = "AnnotationIdentifier"
            var annotationView: MyAnnotationView?                           


            if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "AnnotationIdentifier") as? MyAnnotationView {
                annotationView = dequeuedAnnotationView
                annotationView?.annotation = annotation
            } else {

                // if no views to dequeue, create an Annotation View
                let av = MyAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
                av.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
                annotationView = av     
            }


            if let annotationView = annotationView {
                annotationView.canShowCallout = true                        // callout bubble
                annotationView.image = UIImage(named: "Delivery")
                annotationView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
            }

            return annotationView

        }

    }

The annotationView.image = UIImage(named: "Delivery")

&

AnnotationView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)

are there just to check if the code is working and display a sample view on the map, as they use the standard properties inherited from MKAnnotationView.

I don't know how to make the viewFor annotation method use the XIB I have created. Could anyone please help me with that? I searched for the solution, but only found something relevant in Obj C.

Thank you!


Solution

  • UPDATED CODE BASED ON Sh-Khan's answer

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    
                    //  Don't want to show a custom image if the annotation is the user's location.
                    if (annotation is MKUserLocation) {
                        return nil
                    } else {
    
                        let annotationIdentifier = "AnnotationIdentifier"
                        let nibName = "MyAnnotationView"
                        let viewFromNib = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?.first as! MyAnnotationView
                        var annotationView: MyAnnotationView?
    
                        // if there is a view to be dequeued, use it for the annotation
                        if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) as? MyAnnotationView {
    
                            if dequeuedAnnotationView.subviews.isEmpty {
                                dequeuedAnnotationView.addSubview(viewFromNib)
                            }
                            annotationView = dequeuedAnnotationView
                            annotationView?.annotation = annotation
                        } else {
    
                            // if no views to dequeue, create an Annotation View
                            let av = MyAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
                            av.addSubview(viewFromNib)
                            annotationView = av     // extend scope to be able to return at the end of the func
                        }
    
                        // after we manage to create or dequeue the av, configure it
                        if let annotationView = annotationView {
                            annotationView.canShowCallout = true                                    // callout bubble
                            annotationView.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
                            annotationView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
    
                            let customView = annotationView.subviews.first as! MyAnnotationView
                            customView.frame = annotationView.frame
                            customView.textLabel.text = (annotationView.annotation?.title)!
                        }
                        return annotationView
                    }
    }