0

How can I set a custom view for MKAnnotationView? I want my map pins to look unique via a UIView. That UIView subclass could have other views in it I want to customize.

There are many examples online on how to set the annotations image, but not how to actually change that annotation:

func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView?
    {
        if !(annotation is MKPointAnnotation) {
            return nil
        }

        let annotationIdentifier = "AnnotationIdentifier"
        var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotationIdentifier)

        if annotationView == nil {
            annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
            annotationView!.canShowCallout = true
        }
        else {
            annotationView!.annotation = annotation
        }

        let pinImage = UIImage(named: "customPinImage")
        annotationView!.image = pinImage

       return annotationView   
    }
Micro
  • 10,303
  • 14
  • 82
  • 120

2 Answers2

9

MKAnnotationView is a subclass of UIView that can be subclassed itself.

So you would just need to subclass MKAnnotationView.

Custom Subview

Here a simple example that shows a blue triangle. Since you mentioned that the UIView custom subclass should have other views in it I added a label that should show a number.

class CustomAnnotationView: MKAnnotationView {
    private let annotationFrame = CGRect(x: 0, y: 0, width: 40, height: 40)
    private let label: UILabel

    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        self.label = UILabel(frame: annotationFrame.offsetBy(dx: 0, dy: -6))
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
        self.frame = annotationFrame
        self.label.font = UIFont.systemFont(ofSize: 24, weight: .semibold)
        self.label.textColor = .white
        self.label.textAlignment = .center
        self.backgroundColor = .clear
        self.addSubview(label)
    }

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

    public var number: UInt32 = 0 {
        didSet {
            self.label.text = String(number)
        }
    }

    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }

        context.beginPath()
        context.move(to: CGPoint(x: rect.midX, y: rect.maxY))
        context.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
        context.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
        context.closePath()

        UIColor.blue.set()
        context.fillPath()
    }

}

MKMapViewDelegate method

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

    guard annotation is MKPointAnnotation else { return nil }

    let customAnnotationView = self.customAnnotationView(in: mapView, for: annotation)
    customAnnotationView.number = arc4random_uniform(10)
    return customAnnotationView
}

Custom Annoation View

private func customAnnotationView(in mapView: MKMapView, for annotation: MKAnnotation) -> CustomAnnotationView {
    let identifier = "CustomAnnotationViewID"

    if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? CustomAnnotationView {
        annotationView.annotation = annotation
        return annotationView
    } else {
        let customAnnotationView = CustomAnnotationView(annotation: annotation, reuseIdentifier: identifier)
        customAnnotationView.canShowCallout = true
        return customAnnotationView
    }
}

Result

The result would look like this:

custom annotation view

Stephan Schlecht
  • 26,556
  • 1
  • 33
  • 47
3

I previously used a UIView to annotate a MKAnnotationView. I did this by adding a the view as a subview to the MKAnnotationView but soon found out that this caused a whole load of memory issues when rendering a lot of annotations on my map. Instead I reverted to building a UIView comprised of my different subviews and then converting it into a UIImage and assigning it to the image property of MKAnnotationView.

Here is a link to a Stack Overflow answer that will help with the UIView to UIImage conversion.

Rhuari Glen
  • 497
  • 2
  • 5
  • Good idea! You found that converting `UIView` to `UIImage` wasn't as expensive memory wise? – Micro Jul 04 '18 at 21:43
  • @Micro Ohh definitely. Sometimes my map would have over 200 annotations rendered and using custom views seriously slowed this down. Removing my use of `UIView` and using `MKAnnotation`'s `image` property cut down on my memory footprint. Implementing map pin clustering reduced it even more. – Rhuari Glen Jul 04 '18 at 21:46
  • In part of my view I want to load an image from a url - I worry that with your method the image may not load from the url before it is converted into an `image` and set as `MKAnnotationView`'s `image` .. – Micro Jul 04 '18 at 21:59
  • Will you be downloading lots of different images? If not I would download the single image before rendering the annotations on the map – Rhuari Glen Jul 04 '18 at 22:01