Animating annotations position change in MKMapView

Annotations are mainly used to displays static “pins” in MKMapView but sometimes you might need to make them move and animate their position changes so it looks better to the users.

There are a few things you need to do to achieve this.

Coordinate property specifics

The MKAnnotation protocol has a coordinate property that is used by the MKMapView to position the annotation to its corresponding location on the map.

If you just update the property you will quickly see that nothing happens, the annotation does not move on the map.

MKMapView uses KVO to know when the coordinate property changes so in Swift you need to mark the coordinate property in your MKAnnotation asĀ @objc dynamic to make it work

final class LocationViewModel: NSObject, MKAnnotation {
    @objc dynamic coordinate: CLLocationCoordinate2D

    ...
}

With this change you will notice that the annotation now moves on the map, but it is not smooth, it basically jumps from the old position to the new one.

[Read More]
iOS  Xcode  MapKit 

Clustering annotations in MKMapView

If you need to display many annotations in your MKMapView it is recommended to cluster them for better performance.

Map clustering

This means instead of showing all the visible annotations you group annotations that are close together into one single annotation cluster representing them instead.

This cluster annotation usually shows the number of annotations it represents. As you then zoom in to get finer detail the clusters break up and show the actual annotations.

Clustering is supported in MKMapView on iOS 11 and newer, no need to use any custom library. If you need to support older versions of iOS, there are libraries like Cluster that you can use.

Custom cluster view implementation

Let’s say you use a custom annotation view to show your annotations and you want to add support for clustering. You first create a custom MKAnnotationView in the same way

final class LocationDataMapClusterView: MKAnnotationView {

    // MARK: Initialization
    private let countLabel = UILabel()

    override var annotation: MKAnnotation? {
    	didSet {
			 guard let annotation = annotation as? MKClusterAnnotation else {
            	assertionFailure("Using LocationDataMapClusterView with wrong annotation type")
            	return
        	}

    		countLabel.text = annotation.memberAnnotations.count < 100 ? "\(annotation.memberAnnotations.count)" : "99+"
    	}
    }

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

		displayPriority = .defaultHigh
        collisionMode = .circle

        frame = CGRect(x: 0, y: 0, width: 40, height: 50)
        centerOffset = CGPoint(x: 0, y: -frame.size.height / 2)

       
        setupUI()
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: Setup
    private func setupUI() {
        ...
    }
}

The idea is the same as when custom annotation view, but there is a difference. You need to define displayPriority to tell the map that your cluster annotation has a higher priority that normal annotation.

[Read More]
iOS  Xcode  MapKit 

Using custom annotation views in MKMapView

If you want to display completely custom views as “pins” on the map in your iOS application, you should use annotations. All your data needs to be represented as objects conforming to the MKAnnotation protocol, with title, subtitle and coordinate as the required properties.

Custom view implementation

Visually you represent an MKAnnotation with a MKAnnotationView. You can create a custom class that subclasses MKAnnotationView and implement your custom UI in that class.

Here is an sample MKAnnotationView with fixed size that displays just one custom view

final class LocationAnnotationView: MKAnnotationView, Reusable {

    // MARK: Initialization

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

        frame = CGRect(x: 0, y: 0, width: 40, height: 50)
        centerOffset = CGPoint(x: 0, y: -frame.size.height / 2)

        canShowCallout = true
        setupUI()
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: Setup

    private func setupUI() {
        backgroundColor = .clear

        let view = MapPinView()
        addSubview(view)

        view.frame = bounds
    }
}

The MKAnnotationView is by default aligned to its corresponding position on map with the bottom left corner. If your MKAnnotationView looks like a pin for example, you need to align it to the position on the map with the bottom center point. To do that you use the centerOffset property as shown.

Registering the custom view with MKMapView

The next step is to tell MKMapView to user your custom class.

Using single custom MKAnnotationView

If you want to display only one type of annotation in your MKMapView, just register your custom class with the MKMapViewDefaultAnnotationViewReuseIdentifier reuse identifier

mapView.register(LocationAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)

This is enough to make MKMapView to completely handle creating and recycling instances of your custom MKAnnotationViews for you. No need to implement any MKMapView delegate methods for providing annotation views.

[Read More]
iOS  Xcode  MapKit