---
name: geotoolbox
description: GeoToolbox PlaceDescriptor patterns with MapKit integration for location representation, geocoding, and multi-service place identifiers. Use when working with place descriptors, geocoding, or cross-service location data.
allowed-tools: [Read, Glob, Grep]
last_verified: 2026-07-16
review_by: 2027-06-22
---

# GeoToolbox and PlaceDescriptor Patterns

Portable location representation using `PlaceDescriptor` from the GeoToolbox framework. Covers place construction from coordinates, addresses, and MapKit items; forward and reverse geocoding with the new async APIs; and multi-service place identifiers for cross-platform interop.

## When This Skill Activates

Use this skill when the user:
- Asks about **GeoToolbox** or **PlaceDescriptor**
- Wants to represent a place with coordinates, address, or common name
- Needs to convert between **MKMapItem** and **PlaceDescriptor**
- Asks about **forward geocoding** (address to coordinates) or **reverse geocoding** (coordinates to address)
- Wants to store or transmit **multi-service place identifiers** (Apple Maps, Google Maps, etc.)
- Mentions **PlaceRepresentation**, **SupportingPlaceRepresentation**, or **MKGeocodingRequest**
- Asks about portable or interoperable location data structures

## Decision Tree

```
What do you need?
|
+-- Represent a place with coordinates and/or address
|   +-- From a known coordinate
|   |   +-- PlaceRepresentation.coordinate(CLLocationCoordinate2D)
|   +-- From a known address string
|   |   +-- PlaceRepresentation.address(String)
|   +-- Both coordinate and address
|   |   +-- Pass multiple representations to PlaceDescriptor
|   +-- From an existing MKMapItem
|       +-- PlaceDescriptor(item: MKMapItem)
|
+-- Geocode an address to coordinates
|   +-- MKGeocodingRequest(addressString:)
|   +-- try await request.mapItems
|
+-- Reverse geocode coordinates to an address
|   +-- MKReverseGeocodingRequest(location:)
|   +-- try await request.mapItems
|
+-- Attach service identifiers (Apple Maps, Google, etc.)
|   +-- SupportingPlaceRepresentation.serviceIdentifiers([String: String])
|
+-- Read place properties
    +-- descriptor.coordinate, descriptor.address, descriptor.commonName
    +-- descriptor.serviceIdentifier(for: "com.apple.maps")
```

## API Availability

| API | Minimum OS | Import |
|-----|-----------|--------|
| `PlaceDescriptor` | iOS 26 / macOS 26 | `GeoToolbox` |
| `PlaceRepresentation` | iOS 26 / macOS 26 | `GeoToolbox` |
| `SupportingPlaceRepresentation` | iOS 26 / macOS 26 | `GeoToolbox` |
| `MKGeocodingRequest` | iOS 26 / macOS 26 | `MapKit` |
| `MKReverseGeocodingRequest` | iOS 26 / macOS 26 | `MapKit` |
| `PlaceDescriptor(item:)` | iOS 26 / macOS 26 | `GeoToolbox` + `MapKit` |
| `MKMapItem` | iOS 6 / macOS 10.9 | `MapKit` |
| `CLLocationCoordinate2D` | iOS 2 / macOS 10.6 | `CoreLocation` |

## Quick Start

### Create a PlaceDescriptor from Coordinates

```swift
import GeoToolbox
import CoreLocation

let coordinate = CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)

let descriptor = PlaceDescriptor(
    representations: [.coordinate(coordinate)],
    commonName: "Apple Park"
)

// Read back
if let coord = descriptor.coordinate {
    print("Lat: \(coord.latitude), Lon: \(coord.longitude)")
}
print(descriptor.commonName ?? "No name")
```

### Create a PlaceDescriptor from an Address

```swift
import GeoToolbox

let descriptor = PlaceDescriptor(
    representations: [.address("One Apple Park Way, Cupertino, CA 95014")],
    commonName: "Apple Park"
)

if let address = descriptor.address {
    print("Address: \(address)")
}
```

### Multiple Representations and Service Identifiers

```swift
import GeoToolbox
import CoreLocation

let coordinate = CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)

let descriptor = PlaceDescriptor(
    representations: [
        .coordinate(coordinate),
        .address("One Apple Park Way, Cupertino, CA 95014")
    ],
    commonName: "Apple Park",
    supportingRepresentations: [
        .serviceIdentifiers([
            "com.apple.maps": "apple-maps-id-12345",
            "com.google.maps": "ChIJ-bfVTh8_j4ARDMPaL2Njo3I"
        ])
    ]
)

// Access a specific service identifier
if let appleId = descriptor.serviceIdentifier(for: "com.apple.maps") {
    print("Apple Maps ID: \(appleId)")
}
```

### Convert from MKMapItem

```swift
import GeoToolbox
import MapKit

func descriptorFromMapItem(_ mapItem: MKMapItem) -> PlaceDescriptor {
    PlaceDescriptor(item: mapItem)
}
```

## Forward Geocoding (Address to Coordinates)

```swift
import MapKit

func geocodeAddress(_ addressString: String) async throws -> [MKMapItem] {
    let request = MKGeocodingRequest(addressString: addressString)
    let mapItems = try await request.mapItems
    return mapItems
}

// Usage
let items = try await geocodeAddress("One Apple Park Way, Cupertino, CA")
if let first = items.first {
    let coord = first.placemark.coordinate
    print("Found: \(coord.latitude), \(coord.longitude)")
}
```

## Reverse Geocoding (Coordinates to Address)

```swift
import MapKit
import CoreLocation

func reverseGeocode(_ coordinate: CLLocationCoordinate2D) async throws -> [MKMapItem] {
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
    let request = MKReverseGeocodingRequest(location: location)
    let mapItems = try await request.mapItems
    return mapItems
}

// Usage
let coordinate = CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)
let items = try await reverseGeocode(coordinate)
if let first = items.first {
    print("Address: \(first.placemark.title ?? "Unknown")")
}
```

## Full Integration Example

Geocode an address, convert the result to a PlaceDescriptor with service identifiers, and read back all properties:

```swift
import GeoToolbox
import MapKit
import CoreLocation

func buildPlaceDescriptor(from addressString: String) async throws -> PlaceDescriptor? {
    // Forward geocode
    let request = MKGeocodingRequest(addressString: addressString)
    let mapItems = try await request.mapItems
    guard let mapItem = mapItems.first else { return nil }

    // Convert MKMapItem to PlaceDescriptor
    var descriptor = PlaceDescriptor(item: mapItem)

    // Or build manually with extra data
    let coordinate = mapItem.placemark.coordinate
    descriptor = PlaceDescriptor(
        representations: [
            .coordinate(coordinate),
            .address(addressString)
        ],
        commonName: mapItem.name,
        supportingRepresentations: [
            .serviceIdentifiers([
                "com.apple.maps": "resolved-id-\(coordinate.latitude)"
            ])
        ]
    )

    return descriptor
}

func displayDescriptor(_ descriptor: PlaceDescriptor) {
    if let name = descriptor.commonName {
        print("Name: \(name)")
    }
    if let coord = descriptor.coordinate {
        print("Coordinate: \(coord.latitude), \(coord.longitude)")
    }
    if let address = descriptor.address {
        print("Address: \(address)")
    }
    if let appleId = descriptor.serviceIdentifier(for: "com.apple.maps") {
        print("Apple Maps ID: \(appleId)")
    }
}
```

## Top Mistakes

| # | Mistake | Problem | Fix |
|---|---------|---------|-----|
| 1 | Importing only `MapKit` when using `PlaceDescriptor` | `PlaceDescriptor` lives in `GeoToolbox`, not `MapKit` | Add `import GeoToolbox` alongside `import MapKit` |
| 2 | Hardcoding service identifier keys | Service identifier keys are strings; typos cause silent failures | Define constants for service keys like `"com.apple.maps"` |
| 3 | Not handling geocoding errors | Network or service failures throw errors | Use `do/catch` or `try await` with proper error handling |

## Patterns

### Service Identifier Constants

Define constants to avoid typos in service identifier keys:

```swift
// Good -- constants prevent typos
enum PlaceService {
    static let appleMaps = "com.apple.maps"
    static let googleMaps = "com.google.maps"
    static let foursquare = "com.foursquare"
}

if let id = descriptor.serviceIdentifier(for: PlaceService.appleMaps) {
    // use id
}
```

```swift
// Bad -- raw string literals are error-prone
if let id = descriptor.serviceIdentifier(for: "com.apple.map") { // typo: "map" not "maps"
    // silently nil
}
```

### Nil-Safe Property Access

```swift
// Good -- check each optional property
func formatPlace(_ descriptor: PlaceDescriptor) -> String {
    var parts: [String] = []
    if let name = descriptor.commonName {
        parts.append(name)
    }
    if let address = descriptor.address {
        parts.append(address)
    }
    if let coord = descriptor.coordinate {
        parts.append("\(coord.latitude), \(coord.longitude)")
    }
    return parts.joined(separator: " -- ")
}
```

```swift
// Bad -- force-unwrapping optional properties
let name = descriptor.commonName! // crashes if nil
let coord = descriptor.coordinate! // crashes if no coordinate representation
```

### Geocoding with Fallback

```swift
// Good -- handle empty results and errors
func resolvePlace(_ address: String) async -> PlaceDescriptor? {
    do {
        let request = MKGeocodingRequest(addressString: address)
        let items = try await request.mapItems
        guard let item = items.first else {
            print("No results for address: \(address)")
            return nil
        }
        return PlaceDescriptor(item: item)
    } catch {
        print("Geocoding failed: \(error.localizedDescription)")
        return nil
    }
}
```

```swift
// Bad -- no error handling, no empty check
func resolvePlace(_ address: String) async -> PlaceDescriptor {
    let request = MKGeocodingRequest(addressString: address)
    let items = try! await request.mapItems // crashes on failure
    return PlaceDescriptor(item: items.first!) // crashes if empty
}
```

## Review Checklist

- [ ] `import GeoToolbox` is present when using `PlaceDescriptor`, `PlaceRepresentation`, or `SupportingPlaceRepresentation`
- [ ] `import MapKit` is present when using `MKGeocodingRequest`, `MKReverseGeocodingRequest`, or `MKMapItem`
- [ ] `import CoreLocation` is present when using `CLLocationCoordinate2D` or `CLLocation`
- [ ] `PlaceDescriptor` properties (`coordinate`, `address`, `commonName`) are checked for `nil` before use
- [ ] Service identifier keys use defined constants, not raw string literals
- [ ] `MKReverseGeocodingRequest` receives a `CLLocation`, not a raw `CLLocationCoordinate2D`
- [ ] Geocoding results are checked for empty arrays before accessing elements
- [ ] Geocoding calls use proper `async/await` error handling with `do/catch`
- [ ] Multiple representations are provided when both coordinate and address are known
- [ ] Supporting representations include service identifiers when cross-service interop is needed

## Cross-References

- For **MapKit map views** and annotations, see MapKit documentation
- For **CoreLocation** permissions and location updates, see CoreLocation documentation
- For **SwiftUI integration** with maps, see `Map` view in SwiftUI

## References

- [GeoToolbox Framework](https://developer.apple.com/documentation/geotoolbox)
- [PlaceDescriptor](https://developer.apple.com/documentation/geotoolbox/placedescriptor)
- [MKGeocodingRequest](https://developer.apple.com/documentation/mapkit/mkgeocodingrequest)
- [MKReverseGeocodingRequest](https://developer.apple.com/documentation/mapkit/mkreversegeocodingrequest)
- [MKMapItem](https://developer.apple.com/documentation/mapkit/mkmapitem)


---
Source attribution: rshankras
Original project: https://github.com/rshankras/claude-code-apple-skills/blob/9ffb83138209057875698dd11c1720c657c47a92/skills/mapkit/geotoolbox/SKILL.md
License: MIT
License information: https://opensource.org/license/mit
Original upstream instructions; downloaded from Mega AI Hub.

## Upstream license notice

MIT License

Copyright (c) 2025 Ravishankar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



# Mega AI Hub resource metadata

Creator / publisher: rshankras
Original project: https://github.com/rshankras/claude-code-apple-skills/blob/9ffb83138209057875698dd11c1720c657c47a92/skills/mapkit/geotoolbox/SKILL.md
Attribution basis: Source repository record

Fields of work: Development & Engineering, Product Management
Roles: Backend Developer, Data Engineer, Frontend Developer, Mobile Developer, Product Manager
Specialties: API, Backend, Mobile
Tools: Geotoolbox, Mapkit
Country applicability: Country not specified
Classification basis: Source catalog labels
Instruction SHA-256: 8d20f78f79a30cc6279158db835aa2b23b04b68bb030c9ad79571eff79c41c47
