How do you show places with MapKit: annotations, overlays, regions and clustering?

17.2.2 · L2 · Interview

Predict first

Does a map rect contain a point inside and a point outside, and what span does a 1 km region have?

import MapKit

let rect = MKMapRect(x: 0, y: 0, width: 10, height: 10)
print(rect.contains(MKMapPoint(x: 5, y: 5)), rect.contains(MKMapPoint(x: 15, y: 5)))
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 18.52, longitude: 73.85), latitudinalMeters: 1000, longitudinalMeters: 1000)
print(region.span.latitudeDelta > 0, region.span.latitudeDelta < 0.1)
  • true false, then true true
  • false true, then true false
  • true true, then false false

Mental model. A map view is a table of pins that the system recycles as you pan: only the visible pins exist, and when two pins are too close they merge into one bubble that says how many are inside.

MapKit shows a map and the things on it. In SwiftUI use Map with Marker, Annotation, MapPolyline and MapCircle content builders and a MapCameraPosition binding; in UIKit use MKMapView with a delegate. The vocabulary: a coordinate (CLLocationCoordinate2D, latitude and longitude in degrees); an annotation (a point of interest, a model object conforming to MKAnnotation) rendered by an annotation view that the map reuses like table cells (dequeue by identifier, so thousands of annotations stay cheap); an overlay (a polyline, polygon, circle or tile layer) drawn by a renderer; and a region (MKCoordinateRegion: centre and span in degrees) or map rect (MKMapRect) describing what is visible. Internally the map uses a projected plane (MKMapPoint, with a scale that depends on latitude: MKMapPointsPerMeterAtLatitude), which is why a region built from metres (MKCoordinateRegion(center:latitudinalMeters:longitudinalMeters:)) has a small positive span in degrees, and why MKMapRect.contains(MKMapPoint) is a cheap point-in-rectangle test. Search and routing use MKLocalSearch and MKDirections, geocoding uses CLGeocoder or MKGeocodingRequest (rate limited, so cache results), and Look Around gives street-level imagery through MKLookAroundSceneRequest where available.

Clustering prevents a screenful of overlapping pins: set the annotation view’s clusteringIdentifier (annotations with the same identifier are grouped when they would overlap), return a cluster view for MKClusterAnnotation, and the map handles the merging as you zoom; in SwiftUI use the equivalent Map clustering support or group points yourself. Performance and design: do not add every point at once when you have tens of thousands; load only what is in the visible region (regionDidChange) or cluster server-side, reuse annotation views, avoid heavy custom drawing per annotation, and prefer MKMarkerAnnotationView’s built-in look. Show the user’s location with showsUserLocation (this needs location permission), keep the camera under the user’s control (do not fight their pan with constant re-centring), respect accessibility (annotation labels for VoiceOver), and attribute data when required. Remember that MapKit does the tile loading and caching for you but needs the network, so show a sensible state offline.

A rectangle test is simple geometry. A kilometre is a small fraction of a degree of latitude, so the span is positive and below 0.1 degrees.

Common traps

  • Adding thousands of annotations up front without clustering or view reuse. The map stutters; load by region and cluster.
  • Re-centring the camera whenever data updates. It fights the user’s gestures; move the camera only on explicit intent.

They might follow up with

  • What is a cluster identifier? Annotations sharing a clusteringIdentifier are merged when they overlap on screen.
  • What does a region built from metres return in span? A small span in degrees, scaled by latitude.

Related questions

  • 17.2.1 How does Core Location work: authorization levels, accuracy, significant-change and visits, and geofencing?
  • 17.7.1 How do you approach an unfamiliar Apple framework: what do you check before you write code, and how do you…
  • 7.5.3 How does cell reuse work, and what are the classic reuse bugs?

← How does Core Location work: authorization levels, accuracy…