Check a page for accessibility
Review supplied markup or descriptions for practical accessibility issues and fixes.
MapKit API reference — SwiftUI Map, MKMapView, Marker, Annotation, MKLocalSearch, MKDirections, Look Around, MKMapSnapshotter, clustering, overlays, GeoToolbox PlaceDescriptor, geocoding
---
name: axiom-mapkit-ref
description: MapKit API reference — SwiftUI Map, MKMapView, Marker, Annotation, MKLocalSearch, MKDirections, Look Around, MKMapSnapshotter, clustering, overlays, GeoToolbox PlaceDescriptor, geocoding
license: MIT
metadata:
version: "1.0.0"
last-updated: "2026-02-26"
---
# MapKit API Reference
Complete MapKit API reference for iOS development. Covers both SwiftUI Map (iOS 17+) and MKMapView (UIKit).
## Related Skills
- `axiom-mapkit` — Decision trees, anti-patterns, pressure scenarios
- `axiom-mapkit-diag` — Symptom-based troubleshooting
---
## Part 1: Modern API Overview
| Feature | SwiftUI Map (iOS 17+) | MKMapView |
|---|---|---|
| Declaration | `Map(position:) { content }` | `MKMapView()` |
| Camera control | `MapCameraPosition` binding | `setRegion(_:animated:)` |
| Annotations | `Marker`, `Annotation` in content | `addAnnotation(_:)` + delegate |
| Overlays | `MapCircle`, `MapPolyline`, `MapPolygon` | `addOverlay(_:)` + renderer delegate |
| User location | `UserAnnotation()` | `showsUserLocation = true` |
| Selection | `.mapSelection($selection)` | delegate `didSelect` |
| Controls | `.mapControls { }` | `showsCompass`, `showsScale` |
| Interaction modes | `.mapInteractionModes([])` | delegate methods |
| Clustering | Built-in via `.mapItemClusteringIdentifier` | `MKClusterAnnotation` |
---
## Part 2: SwiftUI Map API
### Basic Map
```swift
@State private var cameraPosition: MapCameraPosition = .automatic
Map(position: $cameraPosition) {
Marker("Home", coordinate: homeCoord)
Annotation("Custom", coordinate: coord) {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
.padding(4)
.background(.blue, in: Circle())
}
UserAnnotation()
MapCircle(center: coord, radius: 500)
.foregroundStyle(.blue.opacity(0.3))
MapPolyline(coordinates: routeCoords)
.stroke(.blue, lineWidth: 3)
}
.mapStyle(.standard(elevation: .realistic))
.mapControls {
MapUserLocationButton()
MapCompass()
MapScaleView()
}
```
### MapCameraPosition
Controls where the camera is positioned:
```swift
// System manages camera to show all content
.automatic
// Specific region
.region(MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
))
// Specific camera with pitch and heading
.camera(MapCamera(
centerCoordinate: coordinate,
distance: 1000, // meters from center
heading: 90, // degrees from north
pitch: 60 // degrees from vertical (0 = top-down)
))
// Follow user location
.userLocation(followsHeading: true, fallback: .automatic)
// Show specific item
.item(mapItem)
// Show specific rect
.rect(MKMapRect(...))
```
#### Programmatic Camera Changes
```swift
// Animate to new position
withAnimation {
cameraPosition = .region(newRegion)
}
// Keyframe animation (iOS 17+)
Map(position: $cameraPosition)
.mapCameraKeyframeAnimator(trigger: flyToTrigger) { initialCamera in
KeyframeTrack(\.centerCoordinate) {
LinearKeyframe(destination, duration: 2.0)
}
KeyframeTrack(\.distance) {
CubicKeyframe(5000, duration: 1.0)
CubicKeyframe(1000, duration: 1.0)
}
}
```
### Map Selection
```swift
@State private var selectedItem: MKMapItem?
Map(position: $cameraPosition, selection: $selectedItem) {
ForEach(mapItems, id: \.self) { item in
Marker(item: item)
}
}
.onChange(of: selectedItem) { _, newItem in
if let newItem {
// Handle selection
}
}
```
### Camera Change Callback
```swift
Map(position: $cameraPosition) { ... }
.onMapCameraChange { context in
// context.region — visible MKCoordinateRegion
// context.camera — current MapCamera
// context.rect — visible MKMapRect
fetchAnnotations(in: context.region)
}
.onMapCameraChange(frequency: .continuous) { context in
// Called during gesture (not just at end)
}
```
### Map Styles
```swift
.mapStyle(.standard) // Default
.mapStyle(.standard(elevation: .realistic)) // 3D buildings
.mapStyle(.standard(emphasis: .muted)) // Muted colors
.mapStyle(.standard(pointsOfInterest: .including([.restaurant, .cafe])))
.mapStyle(.imagery) // Satellite
.mapStyle(.imagery(elevation: .realistic)) // 3D satellite
.mapStyle(.hybrid) // Satellite + labels
.mapStyle(.hybrid(elevation: .realistic)) // 3D hybrid
```
### Interaction Modes
```swift
// Allow all interactions (default)
.mapInteractionModes(.all)
// Read-only map (no interaction)
.mapInteractionModes([])
// Pan only, no zoom
.mapInteractionModes([.pan])
// Pan and zoom, no rotate/pitch
.mapInteractionModes([.pan, .zoom])
```
---
## Part 3: Map Content
### Marker
System-styled map marker with callout:
```swift
// Basic marker
Marker("Coffee Shop", coordinate: coord)
// With system image
Marker("Coffee Shop", systemImage: "cup.and.saucer.fill", coordinate: coord)
// With monogram (2 characters max)
Marker("Coffee Shop", monogram: Text("CS"), coordinate: coord)
// Color
Marker("Coffee Shop", coordinate: coord)
.tint(.brown)
// From MKMapItem
Marker(item: mapItem)
```
### Annotation
Fully custom view at a coordinate:
```swift
Annotation("Custom Pin", coordinate: coord) {
VStack {
Image(systemName: "mappin.circle.fill")
.font(.title)
.foregroundStyle(.red)
Text("Here")
.font(.caption)
}
}
// Anchor point (default is bottom center)
Annotation("Pin", coordinate: coord, anchor: .center) {
Circle()
.fill(.blue)
.frame(width: 20, height: 20)
}
```
### UserAnnotation
Current user location indicator:
```swift
UserAnnotation()
// Custom appearance
UserAnnotation(anchor: .center) {
Image(systemName: "location.circle.fill")
.foregroundStyle(.blue)
}
```
### Shape Overlays
```swift
// Circle
MapCircle(center: coord, radius: 1000) // radius in meters
.foregroundStyle(.blue.opacity(0.2))
.stroke(.blue, lineWidth: 2)
// Polygon
MapPolygon(coordinates: polygonCoords)
.foregroundStyle(.green.opacity(0.3))
.stroke(.green, lineWidth: 2)
// Polyline
MapPolyline(coordinates: routeCoords)
.stroke(.blue, lineWidth: 4)
// From MKRoute
MapPolyline(route.polyline)
.stroke(.blue, lineWidth: 5)
```
### Clustering
```swift
ForEach(locations) { location in
Marker(location.name, coordinate: location.coordinate)
.tag(location.id)
}
.mapItemClusteringIdentifier("locations")
```
---
## Part 4: MKMapView Lifecycle and Delegates
### Creating MKMapView in SwiftUI
```swift
struct MapViewWrapper: UIViewRepresentable {
@Binding var region: MKCoordinateRegion
let annotations: [MKAnnotation]
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.showsUserLocation = true
mapView.register(
MKMarkerAnnotationView.self,
forAnnotationViewWithReuseIdentifier: "marker"
)
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
// Guard against infinite loops
if !regionsAreEqual(mapView.region, region) {
mapView.setRegion(region, animated: true)
}
// Diff annotations instead of removing all
let current = Set(mapView.annotations.compactMap { $0 as? MyAnnotation })
let desired = Set(annotations.compactMap { $0 as? MyAnnotation })
let toAdd = desired.subtracting(current)
let toRemove = current.subtracting(desired)
mapView.addAnnotations(Array(toAdd))
mapView.removeAnnotations(Array(toRemove))
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
static func dismantleUIView(_ mapView: MKMapView, coordinator: Coordinator) {
mapView.removeAnnotations(mapView.annotations)
mapView.removeOverlays(mapView.overlays)
}
}
```
### Key MKMapViewDelegate Methods
```swift
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapViewWrapper
init(_ parent: MapViewWrapper) {
self.parent = parent
}
// Annotation view customization
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else { return nil } // Use default for user
let view = mapView.dequeueReusableAnnotationView(
withIdentifier: "marker",
for: annotation
) as! MKMarkerAnnotationView
view.markerTintColor = .systemRed
view.glyphImage = UIImage(systemName: "mappin")
view.clusteringIdentifier = "poi"
view.canShowCallout = true
return view
}
// Overlay rendering
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let circle = overlay as? MKCircle {
let renderer = MKCircleRenderer(circle: circle)
renderer.fillColor = UIColor.systemBlue.withAlphaComponent(0.2)
renderer.strokeColor = .systemBlue
renderer.lineWidth = 2
return renderer
}
if let polyline = overlay as? MKPolyline {
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = .systemBlue
renderer.lineWidth = 4
return renderer
}
if let polygon = overlay as? MKPolygon {
let renderer = MKPolygonRenderer(polygon: polygon)
renderer.fillColor = UIColor.systemGreen.withAlphaComponent(0.3)
renderer.strokeColor = .systemGreen
renderer.lineWidth = 2
return renderer
}
return MKOverlayRenderer(overlay: overlay)
}
// Region change tracking
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
parent.region = mapView.region
}
// Annotation selection
func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) {
// Handle tap
}
// Cluster annotation
func mapView(
_ mapView: MKMapView,
clusterAnnotationForMemberAnnotations memberAnnotations: [MKAnnotation]
) -> MKClusterAnnotation {
MKClusterAnnotation(memberAnnotations: memberAnnotations)
}
}
```
---
## Part 5: Annotation Types and Customization
### MKMarkerAnnotationView (iOS 11+)
Balloon-shaped marker with glyph:
```swift
let view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "marker")
view.markerTintColor = .systemPurple
view.glyphImage = UIImage(systemName: "star.fill")
view.glyphText = "A" // Text glyph (overrides image)
view.displayPriority = .required // Always visible
view.clusteringIdentifier = "category" // Enable clustering
view.canShowCallout = true
view.titleVisibility = .adaptive // Show title based on space
view.subtitleVisibility = .hidden
```
### MKAnnotationView
Fully custom annotation view:
```swift
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "custom")
view.image = UIImage(named: "custom-pin")
view.centerOffset = CGPoint(x: 0, y: -view.image!.size.height / 2)
view.canShowCallout = true
view.leftCalloutAccessoryView = UIImageView(image: thumbnail)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
```
### Custom Callout
```swift
func mapView(
_ mapView: MKMapView,
annotationView view: MKAnnotationView,
calloutAccessoryControlTapped control: UIControl
) {
guard let annotation = view.annotation as? MyAnnotation else { return }
// Navigate to detail view
}
```
### Annotation View Reuse
Always use `dequeueReusableAnnotationView(withIdentifier:for:)`:
```swift
// Register in makeUIView (once)
mapView.register(MKMarkerAnnotationView.self, forAnnotationViewWithReuseIdentifier: "marker")
// Dequeue in delegate (every time)
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let view = mapView.dequeueReusableAnnotationView(withIdentifier: "marker", for: annotation)
// Configure...
return view
}
```
Without reuse: 1000 annotations = 1000 views in memory.
With reuse: ~20-30 views recycled as user scrolls.
---
## Part 6: MKLocalSearch and MKLocalSearchCompleter
### MKLocalSearchCompleter — Real-Time Autocomplete
```swift
let completer = MKLocalSearchCompleter()
completer.delegate = self
completer.resultTypes = [.pointOfInterest, .address]
completer.region = visibleMapRegion // Bias results to visible area
// Update on each keystroke
completer.queryFragment = "coffee"
// Delegate receives results
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
let results = completer.results // [MKLocalSearchCompletion]
for result in results {
// result.title — "Starbucks"
// result.subtitle — "123 Main St, San Francisco, CA"
// result.titleHighlightRanges — Ranges matching query
}
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
// Network error, rate limit, etc.
}
```
### MKLocalSearch — Full Search
```swift
// From autocomplete completion
let request = MKLocalSearch.Request(completion: selectedCompletion)
// From natural language
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = "coffee shops"
request.region = mapRegion // Bias results
request.resultTypes = .pointOfInterest // Filter type
request.pointOfInterestFilter = MKPointOfInterestFilter(
including: [.cafe, .restaurant]
)
let search = MKLocalSearch(request: request)
let response = try await search.start()
for item in response.mapItems {
// item.name — "Starbucks"
// item.placemark — MKPlacemark with address
// item.placemark.coordinate — CLLocationCoordinate2D
// item.phoneNumber — optional phone
// item.url — optional website
// item.pointOfInterestCategory — .cafe, .restaurant, etc.
}
```
### Result Types
```swift
// Filter what kind of results to return
request.resultTypes = .address // Street addresses only
request.resultTypes = .pointOfInterest // Businesses, landmarks
request.resultTypes = .physicalFeature // Mountains, lakes, parks
request.resultTypes = .query // Suggested search queries (iOS 18+)
request.resultTypes = [.pointOfInterest, .address] // Multiple types
```
### Rate Limiting
- `MKLocalSearchCompleter` handles its own throttling — safe to call on every keystroke
- `MKLocalSearch` — Apple rate-limits these; don't fire more than ~1/second
- If rate-limited, you'll get an error in the completion handler
- Reuse `MKLocalSearchCompleter` instances — don't create new ones per query
---
## Part 7: MKDirections and MKRoute
### Calculate Directions
```swift
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destinationMapItem
request.transportType = .automobile
request.requestsAlternateRoutes = true // Get multiple routes
let directions = MKDirections(request: request)
let response = try await directions.calculate()
for route in response.routes {
route.polyline // MKPolyline — display on map
route.expectedTravelTime // TimeInterval in seconds
route.distance // CLLocationDistance in meters
route.name // "I-280 S" — route name
route.advisoryNotices // [String] — warnings
route.steps // [MKRoute.Step] — turn-by-turn
}
```
### Transport Types
```swift
.automobile // Driving directions
.walking // Pedestrian directions
.transit // Public transit (where available)
.any // All modes
```
### ETA Only (Faster)
```swift
let directions = MKDirections(request: request)
let eta = try await directions.calculateETA()
eta.expectedTravelTime // TimeInterval
eta.distance // CLLocationDistance
eta.expectedArrivalDate // Date
eta.expectedDepartureDate // Date
eta.transportType // MKDirectionsTransportType
```
### Turn-by-Turn Steps
```swift
for step in route.steps {
step.instructions // "Turn right onto Main St"
step.distance // CLLocationDistance in meters
step.polyline // MKPolyline for this step's segment
step.transportType // May change for transit routes
step.notice // Optional advisory
}
```
---
## Part 8: Look Around
### Check Availability
```swift
let request = MKLookAroundSceneRequest(coordinate: coordinate)
do {
let scene = try await request.scene
// scene is non-nil — Look Around available at this coordinate
} catch {
// Look Around not available here
}
```
### SwiftUI
```swift
@State private var lookAroundScene: MKLookAroundScene?
LookAroundPreview(scene: $lookAroundScene)
.frame(height: 200)
// Load scene
func loadLookAround(for coordinate: CLLocationCoordinate2D) async {
let request = MKLookAroundSceneRequest(coordinate: coordinate)
lookAroundScene = try? await request.scene
}
```
### UIKit
```swift
let controller = MKLookAroundViewController(scene: scene)
// Present modally or embed as child view controller
```
### Static Snapshot
```swift
let snapshotter = MKLookAroundSnapshotter(scene: scene, options: .init())
let snapshot = try await snapshotter.snapshot
let image = snapshot.image // UIImage
```
---
## Part 9: Overlays and Renderers
### Adding Overlays (MKMapView)
```swift
// Circle
let circle = MKCircle(center: coordinate, radius: 1000)
mapView.addOverlay(circle)
// Polygon
let polygon = MKPolygon(coordinates: &coords, count: coords.count)
mapView.addOverlay(polygon)
// Polyline
let polyline = MKPolyline(coordinates: &coords, count: coords.count)
mapView.addOverlay(polyline, level: .aboveRoads)
// Custom tile overlay
let template = "https://tile.example.com/{z}/{x}/{y}.png"
let tileOverlay = MKTileOverlay(urlTemplate: template)
tileOverlay.canReplaceMapContent = true // Hides Apple Maps base layer
mapView.addOverlay(tileOverlay, level: .aboveLabels)
```
### Renderer Delegate
```swift
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
switch overlay {
case let circle as MKCircle:
let renderer = MKCircleRenderer(circle: circle)
renderer.fillColor = UIColor.systemBlue.withAlphaComponent(0.2)
renderer.strokeColor = .systemBlue
renderer.lineWidth = 2
return renderer
case let polyline as MKPolyline:
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = .systemBlue
renderer.lineWidth = 4
return renderer
case let polygon as MKPolygon:
let renderer = MKPolygonRenderer(polygon: polygon)
renderer.fillColor = UIColor.systemGreen.withAlphaComponent(0.3)
renderer.strokeColor = .systemGreen
renderer.lineWidth = 2
return renderer
case let tile as MKTileOverlay:
return MKTileOverlayRenderer(tileOverlay: tile)
default:
return MKOverlayRenderer(overlay: overlay)
}
}
```
### Overlay Levels
```swift
mapView.addOverlay(overlay, level: .aboveRoads) // Above roads, below labels
mapView.addOverlay(overlay, level: .aboveLabels) // Above everything
```
### Gradient Polyline
```swift
let renderer = MKGradientPolylineRenderer(polyline: polyline)
renderer.setColors([.green, .yellow, .red], locations: [0.0, 0.5, 1.0])
renderer.lineWidth = 6
```
---
## Part 10: Map Snapshots
Generate static map images for sharing, thumbnails, or offline display:
```swift
let options = MKMapSnapshotter.Options()
options.region = MKCoordinateRegion(
center: coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
)
options.size = CGSize(width: 300, height: 200)
options.scale = UIScreen.main.scale // Retina support
options.mapType = .standard
options.showsBuildings = true
options.pointOfInterestFilter = .excludingAll // Clean map
let snapshotter = MKMapSnapshotter(options: options)
let snapshot = try await snapshotter.start()
let image = snapshot.image
// Draw custom annotations on snapshot
UIGraphicsBeginImageContextWithOptions(image.size, true, image.scale)
image.draw(at: .zero)
let pinImage = UIImage(systemName: "mappin.circle.fill")!
let point = snapshot.point(for: coordinate)
pinImage.draw(at: CGPoint(
x: point.x - pinImage.size.width / 2,
y: point.y - pinImage.size.height
))
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
```
#### Snapshot Coordinate Conversion
```swift
// Convert coordinate to point in snapshot image
let point = snapshot.point(for: coordinate)
// Check if coordinate is in snapshot bounds
let isVisible = CGRect(origin: .zero, size: snapshot.image.size).contains(point)
```
---
## Part 11: iOS Version Feature Matrix
| Feature | iOS Version |
|---|---|
| MKMapView | 3.0+ |
| MKLocalSearch | 6.1+ |
| MKDirections | 7.0+ |
| MKMarkerAnnotationView | 11.0+ |
| MKMapSnapshotter | 7.0+ |
| MKLookAroundSceneRequest | 16.0+ |
| LookAroundPreview (SwiftUI) | 17.0+ |
| SwiftUI Map (content builder) | 17.0+ |
| MapCameraPosition | 17.0+ |
| .mapSelection | 17.0+ |
| .mapCameraKeyframeAnimator | 17.0+ |
| .onMapCameraChange | 17.0+ |
| MapUserLocationButton | 17.0+ |
| MapCompass | 17.0+ |
| MapScaleView | 17.0+ |
| .mapInteractionModes | 17.0+ |
| MKLocalSearch.ResultType.query | 18.0+ |
| GeoToolbox / PlaceDescriptor | 26.0+ |
| MKGeocodingRequest | 26.0+ |
| MKReverseGeocodingRequest | 26.0+ |
| MKAddress | 26.0+ |
---
## Part 12: GeoToolbox and Geocoding
### GeoToolbox Framework
`GeoToolbox` provides `PlaceDescriptor` — a standardized representation of physical locations that works across MapKit and third-party mapping services.
```swift
import GeoToolbox
// From address
let fountain = PlaceDescriptor(
representations: [.address("121-122 James's St \n Dublin 8 \n D08 ET27 \n Ireland")],
commonName: "Obelisk Fountain"
)
// From coordinates
let tower = PlaceDescriptor(
representations: [.coordinate(CLLocationCoordinate2D(latitude: 48.8584, longitude: 2.2945))],
commonName: "Eiffel Tower"
)
// Multiple representations
let statue = PlaceDescriptor(
representations: [
.coordinate(CLLocationCoordinate2D(latitude: 40.6892, longitude: -74.0445)),
.address("Liberty Island, New York, NY 10004, United States")
],
commonName: "Statue of Liberty"
)
// From MKMapItem
let descriptor = PlaceDescriptor(item: mapItem) // Returns optional
```
### PlaceRepresentation
Enum representing a place using common mapping concepts:
| Case | Usage |
|---|---|
| `.coordinate(CLLocationCoordinate2D)` | Latitude/longitude |
| `.address(String)` | Full address string |
Convenience accessors on `PlaceDescriptor`:
```swift
descriptor.coordinate // CLLocationCoordinate2D?
descriptor.address // String?
descriptor.commonName // String?
```
### SupportingPlaceRepresentation
Proprietary identifiers for places from different mapping services:
```swift
let place = PlaceDescriptor(
representations: [.coordinate(CLLocationCoordinate2D(latitude: 51.5074, longitude: -0.1278))],
commonName: "London Eye",
supportingRepresentations: [
.serviceIdentifiers([
"com.apple.maps": "AppleMapsID123",
"com.google.maps": "GoogleMapsID456"
])
]
)
// Retrieve a specific service identifier
let appleID = place.serviceIdentifier(for: "com.apple.maps")
```
### MKGeocodingRequest — Forward Geocoding
Convert an address string to map items (address to coordinates):
```swift
guard let request = MKGeocodingRequest(addressString: "1 Apple Park Way, Cupertino, CA") else {
return
}
let mapItems = try await request.mapItems
```
### MKReverseGeocodingRequest — Reverse Geocoding
Convert coordinates to map items (coordinates to address):
```swift
let location = CLLocation(latitude: 37.3349, longitude: -122.0090)
guard let request = MKReverseGeocodingRequest(location: location) else {
return
}
let mapItems = try await request.mapItems
```
### MKAddress
Structured address type used when creating `MKMapItem` from a `PlaceDescriptor`:
```swift
if let coordinate = descriptor.coordinate {
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
let address = MKAddress()
let mapItem = MKMapItem(location: location, address: address)
}
```
### Geocoding vs MKLocalSearch
| Need | Use |
|---|---|
| Address string to coordinates | `MKGeocodingRequest` |
| Coordinates to address | `MKReverseGeocodingRequest` |
| Natural language place search | `MKLocalSearch` |
| Autocomplete suggestions | `MKLocalSearchCompleter` |
| Cross-service place identifiers | `PlaceDescriptor` with `SupportingPlaceRepresentation` |
---
## Resources
**WWDC**: 2023-10043, 2024-10094
**Docs**: /mapkit, /mapkit/map, /mapkit/mklocalsearch, /mapkit/mkdirections, /geotoolbox, /geotoolbox/placedescriptor, /mapkit/mkgeocodingrequest, /mapkit/mkreversegeocodingrequest, /mapkit/mkaddress
**Skills**: mapkit, mapkit-diag, core-location-ref
---
name: axiom-mapkit-ref
description: MapKit API reference — SwiftUI Map, MKMapView, Marker, Annotation, MKLocalSearch, MKDirections, Look Around, MKMapSnapshotter, clustering, overlays, GeoToolbox PlaceDescriptor, geocoding
license: MIT
metadata:
version: "1.0.0"
last-updated: "2026-02-26"
---
# MapKit API Reference
Complete MapKit API reference for iOS development. Covers both SwiftUI Map (iOS 17+) and MKMapView (UIKit).
## Related Skills
- `axiom-mapkit` — Decision trees, anti-patterns, pressure scenarios
- `axiom-mapkit-diag` — Symptom-based troubleshooting
---
## Part 1: Modern API Overview
| Feature | SwiftUI Map (iOS 17+) | MKMapView |
|---|---|---|
| Declaration | `Map(position:) { content }` | `MKMapView()` |
| Camera control | `MapCameraPosition` binding | `setRegion(_:animated:)` |
| Annotations | `Marker`, `Annotation` in content | `addAnnotation(_:)` + delegate |
| Overlays | `MapCircle`, `MapPolyline`, `MapPolygon` | `addOverlay(_:)` + renderer delegate |
| User location | `UserAnnotation()` | `showsUserLocation = true` |
| Selection | `.mapSelection($selection)` | delegate `didSelect` |
| Controls | `.mapControls { }` | `showsCompass`, `showsScale` |
| Interaction modes | `.mapInteractionModes([])` | delegate methods |
| Clustering | Built-in via `.mapItemClusteringIdentifier` | `MKClusterAnnotation` |
---
## Part 2: SwiftUI Map API
### Basic Map
```swift
@State private var cameraPosition: MapCameraPosition = .automatic
Map(position: $cameraPosition) {
Marker("Home", coordinate: homeCoord)
Annotation("Custom", coordinate: coord) {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
.padding(4)
.background(.blue, in: Circle())
}
UserAnnotation()
MapCircle(center: coord, radius: 500)
.foregroundStyle(.blue.opacity(0.3))
MapPolyline(coordinates: routeCoords)
.stroke(.blue, lineWidth: 3)
}
.mapStyle(.standard(elevation: .realistic))
.mapControls {
MapUserLocationButton()
MapCompass()
MapScaleView()
}
```
### MapCameraPosition
Controls where the camera is positioned:
```swift
// System manages camera to show all content
.automatic
// Specific region
.region(MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
))
// Specific camera with pitch and heading
.camera(MapCamera(
centerCoordinate: coordinate,
distance: 1000, // meters from center
heading: 90, // degrees from north
pitch: 60 // degrees from vertical (0 = top-down)
))
// Follow user location
.userLocation(followsHeading: true, fallback: .automatic)
// Show specific item
.item(mapItem)
// Show specific rect
.rect(MKMapRect(...))
```
#### Programmatic Camera Changes
```swift
// Animate to new position
withAnimation {
cameraPosition = .region(newRegion)
}
// Keyframe animation (iOS 17+)
Map(position: $cameraPosition)
.mapCameraKeyframeAnimator(trigger: flyToTrigger) { initialCamera in
KeyframeTrack(\.centerCoordinate) {
LinearKeyframe(destination, duration: 2.0)
}
KeyframeTrack(\.distance) {
CubicKeyframe(5000, duration: 1.0)
CubicKeyframe(1000, duration: 1.0)
}
}
```
### Map Selection
```swift
@State private var selectedItem: MKMapItem?
Map(position: $cameraPosition, selection: $selectedItem) {
ForEach(mapItems, id: \.self) { item in
Marker(item: item)
}
}
.onChange(of: selectedItem) { _, newItem in
if let newItem {
// Handle selection
}
}
```
### Camera Change Callback
```swift
Map(position: $cameraPosition) { ... }
.onMapCameraChange { context in
// context.region — visible MKCoordinateRegion
// context.camera — current MapCamera
// context.rect — visible MKMapRect
fetchAnnotations(in: context.region)
}
.onMapCameraChange(frequency: .continuous) { context in
// Called during gesture (not just at end)
}
```
### Map Styles
```swift
.mapStyle(.standard) // Default
.mapStyle(.standard(elevation: .realistic)) // 3D buildings
.mapStyle(.standard(emphasis: .muted)) // Muted colors
.mapStyle(.standard(pointsOfInterest: .including([.restaurant, .cafe])))
.mapStyle(.imagery) // Satellite
.mapStyle(.imagery(elevation: .realistic)) // 3D satellite
.mapStyle(.hybrid) // Satellite + labels
.mapStyle(.hybrid(elevation: .realistic)) // 3D hybrid
```
### Interaction Modes
```swift
// Allow all interactions (default)
.mapInteractionModes(.all)
// Read-only map (no interaction)
.mapInteractionModes([])
// Pan only, no zoom
.mapInteractionModes([.pan])
// Pan and zoom, no rotate/pitch
.mapInteractionModes([.pan, .zoom])
```
---
## Part 3: Map Content
### Marker
System-styled map marker with callout:
```swift
// Basic marker
Marker("Coffee Shop", coordinate: coord)
// With system image
Marker("Coffee Shop", systemImage: "cup.and.saucer.fill", coordinate: coord)
// With monogram (2 characters max)
Marker("Coffee Shop", monogram: Text("CS"), coordinate: coord)
// Color
Marker("Coffee Shop", coordinate: coord)
.tint(.brown)
// From MKMapItem
Marker(item: mapItem)
```
### Annotation
Fully custom view at a coordinate:
```swift
Annotation("Custom Pin", coordinate: coord) {
VStack {
Image(systemName: "mappin.circle.fill")
.font(.title)
.foregroundStyle(.red)
Text("Here")
.font(.caption)
}
}
// Anchor point (default is bottom center)
Annotation("Pin", coordinate: coord, anchor: .center) {
Circle()
.fill(.blue)
.frame(width: 20, height: 20)
}
```
### UserAnnotation
Current user location indicator:
```swift
UserAnnotation()
// Custom appearance
UserAnnotation(anchor: .center) {
Image(systemName: "location.circle.fill")
.foregroundStyle(.blue)
}
```
### Shape Overlays
```swift
// Circle
MapCircle(center: coord, radius: 1000) // radius in meters
.foregroundStyle(.blue.opacity(0.2))
.stroke(.blue, lineWidth: 2)
// Polygon
MapPolygon(coordinates: polygonCoords)
.foregroundStyle(.green.opacity(0.3))
.stroke(.green, lineWidth: 2)
// Polyline
MapPolyline(coordinates: routeCoords)
.stroke(.blue, lineWidth: 4)
// From MKRoute
MapPolyline(route.polyline)
.stroke(.blue, lineWidth: 5)
```
### Clustering
```swift
ForEach(locations) { location in
Marker(location.name, coordinate: location.coordinate)
.tag(location.id)
}
.mapItemClusteringIdentifier("locations")
```
---
## Part 4: MKMapView Lifecycle and Delegates
### Creating MKMapView in SwiftUI
```swift
struct MapViewWrapper: UIViewRepresentable {
@Binding var region: MKCoordinateRegion
let annotations: [MKAnnotation]
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.showsUserLocation = true
mapView.register(
MKMarkerAnnotationView.self,
forAnnotationViewWithReuseIdentifier: "marker"
)
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
// Guard against infinite loops
if !regionsAreEqual(mapView.region, region) {
mapView.setRegion(region, animated: true)
}
// Diff annotations instead of removing all
let current = Set(mapView.annotations.compactMap { $0 as? MyAnnotation })
let desired = Set(annotations.compactMap { $0 as? MyAnnotation })
let toAdd = desired.subtracting(current)
let toRemove = current.subtracting(desired)
mapView.addAnnotations(Array(toAdd))
mapView.removeAnnotations(Array(toRemove))
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
static func dismantleUIView(_ mapView: MKMapView, coordinator: Coordinator) {
mapView.removeAnnotations(mapView.annotations)
mapView.removeOverlays(mapView.overlays)
}
}
```
### Key MKMapViewDelegate Methods
```swift
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapViewWrapper
init(_ parent: MapViewWrapper) {
self.parent = parent
}
// Annotation view customization
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else { return nil } // Use default for user
let view = mapView.dequeueReusableAnnotationView(
withIdentifier: "marker",
for: annotation
) as! MKMarkerAnnotationView
view.markerTintColor = .systemRed
view.glyphImage = UIImage(systemName: "mappin")
view.clusteringIdentifier = "poi"
view.canShowCallout = true
return view
}
// Overlay rendering
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let circle = overlay as? MKCircle {
let renderer = MKCircleRenderer(circle: circle)
renderer.fillColor = UIColor.systemBlue.withAlphaComponent(0.2)
renderer.strokeColor = .systemBlue
renderer.lineWidth = 2
return renderer
}
if let polyline = overlay as? MKPolyline {
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = .systemBlue
renderer.lineWidth = 4
return renderer
}
if let polygon = overlay as? MKPolygon {
let renderer = MKPolygonRenderer(polygon: polygon)
renderer.fillColor = UIColor.systemGreen.withAlphaComponent(0.3)
renderer.strokeColor = .systemGreen
renderer.lineWidth = 2
return renderer
}
return MKOverlayRenderer(overlay: overlay)
}
// Region change tracking
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
parent.region = mapView.region
}
// Annotation selection
func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) {
// Handle tap
}
// Cluster annotation
func mapView(
_ mapView: MKMapView,
clusterAnnotationForMemberAnnotations memberAnnotations: [MKAnnotation]
) -> MKClusterAnnotation {
MKClusterAnnotation(memberAnnotations: memberAnnotations)
}
}
```
---
## Part 5: Annotation Types and Customization
### MKMarkerAnnotationView (iOS 11+)
Balloon-shaped marker with glyph:
```swift
let view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "marker")
view.markerTintColor = .systemPurple
view.glyphImage = UIImage(systemName: "star.fill")
view.glyphText = "A" // Text glyph (overrides image)
view.displayPriority = .required // Always visible
view.clusteringIdentifier = "category" // Enable clustering
view.canShowCallout = true
view.titleVisibility = .adaptive // Show title based on space
view.subtitleVisibility = .hidden
```
### MKAnnotationView
Fully custom annotation view:
```swift
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "custom")
view.image = UIImage(named: "custom-pin")
view.centerOffset = CGPoint(x: 0, y: -view.image!.size.height / 2)
view.canShowCallout = true
view.leftCalloutAccessoryView = UIImageView(image: thumbnail)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
```
### Custom Callout
```swift
func mapView(
_ mapView: MKMapView,
annotationView view: MKAnnotationView,
calloutAccessoryControlTapped control: UIControl
) {
guard let annotation = view.annotation as? MyAnnotation else { return }
// Navigate to detail view
}
```
### Annotation View Reuse
Always use `dequeueReusableAnnotationView(withIdentifier:for:)`:
```swift
// Register in makeUIView (once)
mapView.register(MKMarkerAnnotationView.self, forAnnotationViewWithReuseIdentifier: "marker")
// Dequeue in delegate (every time)
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let view = mapView.dequeueReusableAnnotationView(withIdentifier: "marker", for: annotation)
// Configure...
return view
}
```
Without reuse: 1000 annotations = 1000 views in memory.
With reuse: ~20-30 views recycled as user scrolls.
---
## Part 6: MKLocalSearch and MKLocalSearchCompleter
### MKLocalSearchCompleter — Real-Time Autocomplete
```swift
let completer = MKLocalSearchCompleter()
completer.delegate = self
completer.resultTypes = [.pointOfInterest, .address]
completer.region = visibleMapRegion // Bias results to visible area
// Update on each keystroke
completer.queryFragment = "coffee"
// Delegate receives results
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
let results = completer.results // [MKLocalSearchCompletion]
for result in results {
// result.title — "Starbucks"
// result.subtitle — "123 Main St, San Francisco, CA"
// result.titleHighlightRanges — Ranges matching query
}
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
// Network error, rate limit, etc.
}
```
### MKLocalSearch — Full Search
```swift
// From autocomplete completion
let request = MKLocalSearch.Request(completion: selectedCompletion)
// From natural language
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = "coffee shops"
request.region = mapRegion // Bias results
request.resultTypes = .pointOfInterest // Filter type
request.pointOfInterestFilter = MKPointOfInterestFilter(
including: [.cafe, .restaurant]
)
let search = MKLocalSearch(request: request)
let response = try await search.start()
for item in response.mapItems {
// item.name — "Starbucks"
// item.placemark — MKPlacemark with address
// item.placemark.coordinate — CLLocationCoordinate2D
// item.phoneNumber — optional phone
// item.url — optional website
// item.pointOfInterestCategory — .cafe, .restaurant, etc.
}
```
### Result Types
```swift
// Filter what kind of results to return
request.resultTypes = .address // Street addresses only
request.resultTypes = .pointOfInterest // Businesses, landmarks
request.resultTypes = .physicalFeature // Mountains, lakes, parks
request.resultTypes = .query // Suggested search queries (iOS 18+)
request.resultTypes = [.pointOfInterest, .address] // Multiple types
```
### Rate Limiting
- `MKLocalSearchCompleter` handles its own throttling — safe to call on every keystroke
- `MKLocalSearch` — Apple rate-limits these; don't fire more than ~1/second
- If rate-limited, you'll get an error in the completion handler
- Reuse `MKLocalSearchCompleter` instances — don't create new ones per query
---
## Part 7: MKDirections and MKRoute
### Calculate Directions
```swift
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destinationMapItem
request.transportType = .automobile
request.requestsAlternateRoutes = true // Get multiple routes
let directions = MKDirections(request: request)
let response = try await directions.calculate()
for route in response.routes {
route.polyline // MKPolyline — display on map
route.expectedTravelTime // TimeInterval in seconds
route.distance // CLLocationDistance in meters
route.name // "I-280 S" — route name
route.advisoryNotices // [String] — warnings
route.steps // [MKRoute.Step] — turn-by-turn
}
```
### Transport Types
```swift
.automobile // Driving directions
.walking // Pedestrian directions
.transit // Public transit (where available)
.any // All modes
```
### ETA Only (Faster)
```swift
let directions = MKDirections(request: request)
let eta = try await directions.calculateETA()
eta.expectedTravelTime // TimeInterval
eta.distance // CLLocationDistance
eta.expectedArrivalDate // Date
eta.expectedDepartureDate // Date
eta.transportType // MKDirectionsTransportType
```
### Turn-by-Turn Steps
```swift
for step in route.steps {
step.instructions // "Turn right onto Main St"
step.distance // CLLocationDistance in meters
step.polyline // MKPolyline for this step's segment
step.transportType // May change for transit routes
step.notice // Optional advisory
}
```
---
## Part 8: Look Around
### Check Availability
```swift
let request = MKLookAroundSceneRequest(coordinate: coordinate)
do {
let scene = try await request.scene
// scene is non-nil — Look Around available at this coordinate
} catch {
// Look Around not available here
}
```
### SwiftUI
```swift
@State private var lookAroundScene: MKLookAroundScene?
LookAroundPreview(scene: $lookAroundScene)
.frame(height: 200)
// Load scene
func loadLookAround(for coordinate: CLLocationCoordinate2D) async {
let request = MKLookAroundSceneRequest(coordinate: coordinate)
lookAroundScene = try? await request.scene
}
```
### UIKit
```swift
let controller = MKLookAroundViewController(scene: scene)
// Present modally or embed as child view controller
```
### Static Snapshot
```swift
let snapshotter = MKLookAroundSnapshotter(scene: scene, options: .init())
let snapshot = try await snapshotter.snapshot
let image = snapshot.image // UIImage
```
---
## Part 9: Overlays and Renderers
### Adding Overlays (MKMapView)
```swift
// Circle
let circle = MKCircle(center: coordinate, radius: 1000)
mapView.addOverlay(circle)
// Polygon
let polygon = MKPolygon(coordinates: &coords, count: coords.count)
mapView.addOverlay(polygon)
// Polyline
let polyline = MKPolyline(coordinates: &coords, count: coords.count)
mapView.addOverlay(polyline, level: .aboveRoads)
// Custom tile overlay
let template = "https://tile.example.com/{z}/{x}/{y}.png"
let tileOverlay = MKTileOverlay(urlTemplate: template)
tileOverlay.canReplaceMapContent = true // Hides Apple Maps base layer
mapView.addOverlay(tileOverlay, level: .aboveLabels)
```
### Renderer Delegate
```swift
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
switch overlay {
case let circle as MKCircle:
let renderer = MKCircleRenderer(circle: circle)
renderer.fillColor = UIColor.systemBlue.withAlphaComponent(0.2)
renderer.strokeColor = .systemBlue
renderer.lineWidth = 2
return renderer
case let polyline as MKPolyline:
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = .systemBlue
renderer.lineWidth = 4
return renderer
case let polygon as MKPolygon:
let renderer = MKPolygonRenderer(polygon: polygon)
renderer.fillColor = UIColor.systemGreen.withAlphaComponent(0.3)
renderer.strokeColor = .systemGreen
renderer.lineWidth = 2
return renderer
case let tile as MKTileOverlay:
return MKTileOverlayRenderer(tileOverlay: tile)
default:
return MKOverlayRenderer(overlay: overlay)
}
}
```
### Overlay Levels
```swift
mapView.addOverlay(overlay, level: .aboveRoads) // Above roads, below labels
mapView.addOverlay(overlay, level: .aboveLabels) // Above everything
```
### Gradient Polyline
```swift
let renderer = MKGradientPolylineRenderer(polyline: polyline)
renderer.setColors([.green, .yellow, .red], locations: [0.0, 0.5, 1.0])
renderer.lineWidth = 6
```
---
## Part 10: Map Snapshots
Generate static map images for sharing, thumbnails, or offline display:
```swift
let options = MKMapSnapshotter.Options()
options.region = MKCoordinateRegion(
center: coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
)
options.size = CGSize(width: 300, height: 200)
options.scale = UIScreen.main.scale // Retina support
options.mapType = .standard
options.showsBuildings = true
options.pointOfInterestFilter = .excludingAll // Clean map
let snapshotter = MKMapSnapshotter(options: options)
let snapshot = try await snapshotter.start()
let image = snapshot.image
// Draw custom annotations on snapshot
UIGraphicsBeginImageContextWithOptions(image.size, true, image.scale)
image.draw(at: .zero)
let pinImage = UIImage(systemName: "mappin.circle.fill")!
let point = snapshot.point(for: coordinate)
pinImage.draw(at: CGPoint(
x: point.x - pinImage.size.width / 2,
y: point.y - pinImage.size.height
))
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
```
#### Snapshot Coordinate Conversion
```swift
// Convert coordinate to point in snapshot image
let point = snapshot.point(for: coordinate)
// Check if coordinate is in snapshot bounds
let isVisible = CGRect(origin: .zero, size: snapshot.image.size).contains(point)
```
---
## Part 11: iOS Version Feature Matrix
| Feature | iOS Version |
|---|---|
| MKMapView | 3.0+ |
| MKLocalSearch | 6.1+ |
| MKDirections | 7.0+ |
| MKMarkerAnnotationView | 11.0+ |
| MKMapSnapshotter | 7.0+ |
| MKLookAroundSceneRequest | 16.0+ |
| LookAroundPreview (SwiftUI) | 17.0+ |
| SwiftUI Map (content builder) | 17.0+ |
| MapCameraPosition | 17.0+ |
| .mapSelection | 17.0+ |
| .mapCameraKeyframeAnimator | 17.0+ |
| .onMapCameraChange | 17.0+ |
| MapUserLocationButton | 17.0+ |
| MapCompass | 17.0+ |
| MapScaleView | 17.0+ |
| .mapInteractionModes | 17.0+ |
| MKLocalSearch.ResultType.query | 18.0+ |
| GeoToolbox / PlaceDescriptor | 26.0+ |
| MKGeocodingRequest | 26.0+ |
| MKReverseGeocodingRequest | 26.0+ |
| MKAddress | 26.0+ |
---
## Part 12: GeoToolbox and Geocoding
### GeoToolbox Framework
`GeoToolbox` provides `PlaceDescriptor` — a standardized representation of physical locations that works across MapKit and third-party mapping services.
```swift
import GeoToolbox
// From address
let fountain = PlaceDescriptor(
representations: [.address("121-122 James's St \n Dublin 8 \n D08 ET27 \n Ireland")],
commonName: "Obelisk Fountain"
)
// From coordinates
let tower = PlaceDescriptor(
representations: [.coordinate(CLLocationCoordinate2D(latitude: 48.8584, longitude: 2.2945))],
commonName: "Eiffel Tower"
)
// Multiple representations
let statue = PlaceDescriptor(
representations: [
.coordinate(CLLocationCoordinate2D(latitude: 40.6892, longitude: -74.0445)),
.address("Liberty Island, New York, NY 10004, United States")
],
commonName: "Statue of Liberty"
)
// From MKMapItem
let descriptor = PlaceDescriptor(item: mapItem) // Returns optional
```
### PlaceRepresentation
Enum representing a place using common mapping concepts:
| Case | Usage |
|---|---|
| `.coordinate(CLLocationCoordinate2D)` | Latitude/longitude |
| `.address(String)` | Full address string |
Convenience accessors on `PlaceDescriptor`:
```swift
descriptor.coordinate // CLLocationCoordinate2D?
descriptor.address // String?
descriptor.commonName // String?
```
### SupportingPlaceRepresentation
Proprietary identifiers for places from different mapping services:
```swift
let place = PlaceDescriptor(
representations: [.coordinate(CLLocationCoordinate2D(latitude: 51.5074, longitude: -0.1278))],
commonName: "London Eye",
supportingRepresentations: [
.serviceIdentifiers([
"com.apple.maps": "AppleMapsID123",
"com.google.maps": "GoogleMapsID456"
])
]
)
// Retrieve a specific service identifier
let appleID = place.serviceIdentifier(for: "com.apple.maps")
```
### MKGeocodingRequest — Forward Geocoding
Convert an address string to map items (address to coordinates):
```swift
guard let request = MKGeocodingRequest(addressString: "1 Apple Park Way, Cupertino, CA") else {
return
}
let mapItems = try await request.mapItems
```
### MKReverseGeocodingRequest — Reverse Geocoding
Convert coordinates to map items (coordinates to address):
```swift
let location = CLLocation(latitude: 37.3349, longitude: -122.0090)
guard let request = MKReverseGeocodingRequest(location: location) else {
return
}
let mapItems = try await request.mapItems
```
### MKAddress
Structured address type used when creating `MKMapItem` from a `PlaceDescriptor`:
```swift
if let coordinate = descriptor.coordinate {
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
let address = MKAddress()
let mapItem = MKMapItem(location: location, address: address)
}
```
### Geocoding vs MKLocalSearch
| Need | Use |
|---|---|
| Address string to coordinates | `MKGeocodingRequest` |
| Coordinates to address | `MKReverseGeocodingRequest` |
| Natural language place search | `MKLocalSearch` |
| Autocomplete suggestions | `MKLocalSearchCompleter` |
| Cross-service place identifiers | `PlaceDescriptor` with `SupportingPlaceRepresentation` |
---
## Resources
**WWDC**: 2023-10043, 2024-10094
**Docs**: /mapkit, /mapkit/map, /mapkit/mklocalsearch, /mapkit/mkdirections, /geotoolbox, /geotoolbox/placedescriptor, /mapkit/mkgeocodingrequest, /mapkit/mkreversegeocodingrequest, /mapkit/mkaddress
**Skills**: mapkit, mapkit-diag, core-location-ref
Imported from the listed AgentSkill.sh category. Complete supplied package files and notices are retained. Required external tools and credentials are not installed. No bundled code was executed during import. Individual agent task behavior has not been tested.