Apple MapKit Integration
Help developers integrate MapKit into iOS/macOS/visionOS apps using SwiftUI or UIKit.
Quick Reference
-
Full API documentation: See references/MapKit.md for complete MapKit API details
-
Default to SwiftUI for new projects (iOS 17+), offer UIKit for older targets or specific needs
Common Workflows
- Display a Basic Map
SwiftUI (iOS 17+)
import MapKit import SwiftUI
struct ContentView: View { var body: some View { Map() } }
SwiftUI with initial position
struct ContentView: View { @State private var position: MapCameraPosition = .region( MKCoordinateRegion( center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1) ) )
var body: some View {
Map(position: $position)
}
}
UIKit
import MapKit import UIKit
class MapViewController: UIViewController { private let mapView = MKMapView()
override func viewDidLoad() {
super.viewDidLoad()
mapView.frame = view.bounds
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(mapView)
let region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
)
mapView.setRegion(region, animated: false)
}
}
- Show User Location
Required: Add NSLocationWhenInUseUsageDescription to Info.plist
SwiftUI (iOS 17+)
import CoreLocation import MapKit import SwiftUI
struct ContentView: View { @State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
var body: some View {
Map(position: $position) {
UserAnnotation()
}
.mapControls {
MapUserLocationButton()
MapCompass()
}
}
}
UIKit
class MapViewController: UIViewController, CLLocationManagerDelegate { private let mapView = MKMapView() private let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
setupMapView()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if manager.authorizationStatus == .authorizedWhenInUse {
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
}
}
}
- Add Annotations/Markers
SwiftUI (iOS 17+)
struct Place: Identifiable { let id = UUID() let name: String let coordinate: CLLocationCoordinate2D }
struct ContentView: View { let places = [ Place(name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)), Place(name: "Oakland", coordinate: CLLocationCoordinate2D(latitude: 37.8044, longitude: -122.2712)) ]
var body: some View {
Map {
ForEach(places) { place in
Marker(place.name, coordinate: place.coordinate)
}
}
}
}
Custom annotation content
Map { ForEach(places) { place in Annotation(place.name, coordinate: place.coordinate) { VStack { Image(systemName: "mappin.circle.fill") .font(.title) .foregroundStyle(.red) Text(place.name) .font(.caption) } } } }
UIKit
class PlaceAnnotation: NSObject, MKAnnotation { let title: String? let coordinate: CLLocationCoordinate2D
init(title: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.coordinate = coordinate
}
}
// In view controller: let annotation = PlaceAnnotation( title: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194) ) mapView.addAnnotation(annotation)
- Marker/Annotation Clustering
SwiftUI (iOS 17+) — Use MapContentBuilder with .annotationTitles(.hidden) for clustering behavior, or use MKClusterAnnotation in UIKit.
UIKit with clustering
class ClusterableAnnotation: NSObject, MKAnnotation { let coordinate: CLLocationCoordinate2D let title: String?
init(coordinate: CLLocationCoordinate2D, title: String?) {
self.coordinate = coordinate
self.title = title
}
}
class MapViewController: UIViewController, MKMapViewDelegate { private let mapView = MKMapView()
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
mapView.register(MKMarkerAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
mapView.register(MKMarkerAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else { return nil }
if let cluster = annotation as? MKClusterAnnotation {
let view = mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier, for: annotation) as! MKMarkerAnnotationView
view.markerTintColor = .blue
view.glyphText = "\(cluster.memberAnnotations.count)"
return view
}
let view = mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier, for: annotation) as! MKMarkerAnnotationView
view.clusteringIdentifier = "places" // Enable clustering
view.markerTintColor = .red
return view
}
}
- Directions and Routing
SwiftUI (iOS 17+)
struct DirectionsView: View { @State private var route: MKRoute? @State private var position: MapCameraPosition = .automatic
let start = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)
let end = CLLocationCoordinate2D(latitude: 37.8044, longitude: -122.2712)
var body: some View {
Map(position: $position) {
Marker("Start", coordinate: start)
Marker("End", coordinate: end)
if let route {
MapPolyline(route.polyline)
.stroke(.blue, lineWidth: 5)
}
}
.task {
await calculateRoute()
}
}
func calculateRoute() async {
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: start))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: end))
request.transportType = .automobile
let directions = MKDirections(request: request)
if let response = try? await directions.calculate() {
route = response.routes.first
}
}
}
UIKit
func calculateAndDisplayRoute(from source: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D) { let request = MKDirections.Request() request.source = MKMapItem(placemark: MKPlacemark(coordinate: source)) request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destination)) request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { [weak self] response, error in
guard let route = response?.routes.first else { return }
self?.mapView.addOverlay(route.polyline)
self?.mapView.setVisibleMapRect(route.polyline.boundingMapRect, edgePadding: UIEdgeInsets(top: 50, left: 50, bottom: 50, right: 50), animated: true)
}
}
// MKMapViewDelegate func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { if let polyline = overlay as? MKPolyline { let renderer = MKPolylineRenderer(polyline: polyline) renderer.strokeColor = .systemBlue renderer.lineWidth = 5 return renderer } return MKOverlayRenderer(overlay: overlay) }
- Local Search (Find Places)
func searchForPlaces(query: String, region: MKCoordinateRegion) async -> [MKMapItem] { let request = MKLocalSearch.Request() request.naturalLanguageQuery = query request.region = region
let search = MKLocalSearch(request: request)
if let response = try? await search.start() {
return response.mapItems
}
return []
}
// Usage let results = await searchForPlaces(query: "coffee", region: mapView.region) for item in results { print("(item.name ?? "") - (item.placemark.coordinate)") }
Search completions (autocomplete)
class SearchCompleter: NSObject, ObservableObject, MKLocalSearchCompleterDelegate { @Published var results: [MKLocalSearchCompletion] = [] private let completer = MKLocalSearchCompleter()
override init() {
super.init()
completer.delegate = self
completer.resultTypes = [.address, .pointOfInterest]
}
func search(query: String) {
completer.queryFragment = query
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
results = completer.results
}
}
- Map Overlays (Polylines, Polygons, Circles)
SwiftUI (iOS 17+)
Map { // Polyline MapPolyline(coordinates: [ CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), CLLocationCoordinate2D(latitude: 37.8044, longitude: -122.2712) ]) .stroke(.blue, lineWidth: 3)
// Circle
MapCircle(center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), radius: 1000)
.foregroundStyle(.blue.opacity(0.3))
.stroke(.blue, lineWidth: 2)
// Polygon
MapPolygon(coordinates: [
CLLocationCoordinate2D(latitude: 37.77, longitude: -122.42),
CLLocationCoordinate2D(latitude: 37.78, longitude: -122.40),
CLLocationCoordinate2D(latitude: 37.76, longitude: -122.40)
])
.foregroundStyle(.green.opacity(0.3))
.stroke(.green, lineWidth: 2)
}
- Map Configuration
SwiftUI
Map { // content } .mapStyle(.standard) // .imagery, .hybrid, .standard(elevation: .realistic) .mapControls { MapUserLocationButton() MapCompass() MapScaleView() MapPitchToggle() }
UIKit
mapView.mapType = .standard // .satellite, .hybrid, .satelliteFlyover, .hybridFlyover mapView.showsCompass = true mapView.showsScale = true mapView.isZoomEnabled = true mapView.isScrollEnabled = true mapView.isPitchEnabled = true mapView.isRotateEnabled = true
Key Considerations
-
Privacy: Always add location usage descriptions to Info.plist
-
Maps capability: Enable in Xcode under Signing & Capabilities for directions
-
iOS version: SwiftUI Map API significantly improved in iOS 17; use UIKit for older targets
-
Clustering: Set clusteringIdentifier on annotation views (UIKit) to enable automatic clustering
-
Performance: For many annotations (1000+), consider clustering or custom tile overlays
When to Consult Full Reference
Search references/MapKit.md for:
-
Complete API signatures and parameters
-
LookAround implementation details
-
MKMapItem and place details
-
GeoJSON decoding
-
Indoor mapping (IMDF)
-
Point of interest categories and filtering
-
Snapshot generation
-
All MKMapViewDelegate methods