# Axiom Mapkit Ref

> MapKit API reference — SwiftUI Map, MKMapView, Marker, Annotation, MKLocalSearch, MKDirections, Look Around, MKMapSnapshotter, clustering, overlays, GeoToolbox PlaceDescriptor, geocoding

- Skill: `charleswiltgen/axiom-mapkit-ref` (Agent Skill)
- Install (CLI): `npx skillmds@latest add charleswiltgen/axiom-mapkit-ref`
- Raw SKILL.md: https://api.skillmd.com/api/skills/charleswiltgen/axiom-mapkit-ref/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: charleswiltgen (https://skillmd.com/u/charleswiltgen)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/charleswiltgen/axiom-mapkit-ref

---


# 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


