How to Stop MapKit Annotation Clustering

The easy way to prevent annotations from disappearing on zoom

Colby Hill
Aug 13 · 6 min read
MapView with 3 markers shows them not clustering.
MapView with 3 markers shows them not clustering.

I am the creator of Outdoorist, an outdoor adventure tracking app. I decided to share how I built this functionality into my app in case anyone else needs to make sure that all of their annotations are always visible. I was looking for an easy solution to this for a while, but I wanted to avoid hacky methods to get it working. After scouring documentation and testing out many solutions, I finally found a solution I was satisfied with.

I am going to keep everything as straightforward as possible so that this is useful to anyone. Let’s dive into that now:


Getting Started

Let’s start by creating a new single view application Xcode Project. The only major thing to note is that we will be using the Storyboard User Interface instead of SwiftUI.

Single View Application Configuration Options. Default except changed UI setting to Storyboard.
Single View Application Configuration Options. Default except changed UI setting to Storyboard.

Open the Main.storyboard file and drag a MapKitView to the center of the screen.

After that, add some constraints to the view by selecting the Add New Constraints button and adding four constraints. Constrain all sides of the view to be 0, and uncheck the constrain to margins button.

Here’s an image of what this should look like:

Photo reviewing the constraints to be added
Photo reviewing the constraints to be added

Now let’s add a connection to the ViewController:

  • Use Split view in Xcode to open the ViewController.swift file alongside the Storyboard that is currently open.
  • Control-Drag from the MKMapView to the ViewController file to create an IBOutlet connection.

I’ll name that connection mapView, and connect it to the ViewController.

Screenshot of the add IBOutlet workflow in Xcode.
Screenshot of the add IBOutlet workflow in Xcode.

Now that we have that connection, we can get started with the mapping!


Configuring the Map View

  • Add the MapKit dependency to the project by adding import MapKit to the top of the file, right under import UIKit
  • Add the subclass MKMapViewDelegate to make the ViewController the delegate for our map view.
  • Under super.viewDidLoad() add the line mapView.delegate = self this line specifies the ViewController as the delegate for the map view. Essentially, that just means we’re telling the MapView that we want to control it from this class.

The ViewController.swift should now look like:

import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
@IBOutlet weak var mapView: MKMapView!

override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
}
}

Adding Annotations to the Map

Now that the Map View has been configured, it’s time to start adding annotations to the map.

Before we can add the annotations, we need to set the region of the map view first. The region controls where the center of the map is, and the zoom level desired for the map.

Let’s configure the region for our example, which will involve plotting the San Francisco offices of Google, Microsoft, and Amazon. In this example, we will use the center as a midpoint between the three offices in downtown SF.

Let’s get started with this by creating a function called placePins() and adding code that specifies the center coordinates, zoom, and then sets the region. I’ll show that code now and then dive into it below:

func placePins() {
let center = CLLocationCoordinate2D(latitude: 37.791066, longitude: -122.398183)
let span = MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
let region = MKCoordinateRegion(center: center, span: span)
mapView.setRegion(region, animated: true)
}
  • The first line of the function specifies the coordinates for what we want to be the center of the map view.
  • The second line sets the span for the map view, which is just a specification for how much of the map to be included, i.e. the zoom level.
  • The third and fourth line use the center coordinates and span we just specified to create the MKCoordinateRegion object then set that to be the region used by the map view.

Next, let’s add the three annotations to the map. First we will create an instance of an MKPointAnnotation object, then give it a coordinate and title, and finally add the annotation to the mapView. This can be done with the following code:

let googleAnnotation = MKPointAnnotation()
googleAnnotation.coordinate = CLLocationCoordinate2D(latitude: 37.789911, longitude: -122.390098)
googleAnnotation.title = "Google SF Office"
self.mapView.addAnnotation(googleAnnotation)

Repeat this process for the other two locations using their coordinates. I will provide the code for the other two here:

let microsoftAnnotation = MKPointAnnotation()
microsoftAnnotation.coordinate = CLLocationCoordinate2D(latitude: 37.792146, longitude: -122.403962)
microsoftAnnotation.title = "Microsoft SF Office"
self.mapView.addAnnotation(microsoftAnnotation)
let amazonAnnotation = MKPointAnnotation()
amazonAnnotation.coordinate = CLLocationCoordinate2D(latitude: 37.791366, longitude: -122.392970)
amazonAnnotation.title = "Amazon SF Office"
self.mapView.addAnnotation(amazonAnnotation)

Now that we have the map view configured and our annotations ready to be placed on the map, add a call to the placePins method in viewDidLoad() by adding the line self.placePins()

Now we can run the app for the first time and see our app.

Map view showing the three pins being clustered into one.
Map view showing the three pins being clustered into one.

Now you can see the problem, our three pins are being clustered into one. While we don’t have to do much, we’ll have to add some more code to fix this.


Prevent Annotation Clustering

To do this, we’ll need to create a custom Annotation object.

Create a new file by right-clicking on the project folder → New File Cocoa Touch Class. Give our new object a name of NonClusteringMKMarkerAnnotationView and change the subclass to MKMarkerAnnotationView and click Next through the rest of the menus.

Add the import MapKit line at the top of the file, otherwise you’ll see an error about using an undeclared type.

We’re almost done now, but we have to override the annotation of the MKMarkerAnnotationView to change the displayPriority attribute to always be required. Here’s the code to do that:

override var annotation: MKAnnotation? {
willSet {
displayPriority = MKFeatureDisplayPriority.required
}
}

Now that we have a custom annotation view, there’s just one other method to add in the ViewController.swift file which will allow us to swap out each MKAnnotationView for a NonClusteringMKMarkerAnnotationView.

This is thankfully very simple, we just create an instance of our custom annotation view and pass in the annotation view the map is trying to load.

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
return NonClusteringMKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "MyMarker")
}

Success

Non clustering points on map.
Non clustering points on map.

Success. Now you can see that even when annotations overlap, they are always shown. Even though Amazon’s annotation is being covered almost entirely, it still shows up behind Google. While Clustering is great and definitely helps prevent your UI from looking crowded, in certain cases where you want to see all annotations, no matter what. This is a great solution to that problem! Has anyone else had this problem before?

Thanks for reading! I hope this was helpful for you. If you have any questions, feel free to leave a comment.

I stored all of the for this project in a GitHub repository you can see here

The Startup

Medium's largest active publication, followed by +705K people. Follow to join our community.

Colby Hill

Written by

Software Engineer / Creator of Outdoorist / Travel Enthusiast

The Startup

Medium's largest active publication, followed by +705K people. Follow to join our community.

Colby Hill

Written by

Software Engineer / Creator of Outdoorist / Travel Enthusiast

The Startup

Medium's largest active publication, followed by +705K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store