| name | mapkit-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. |
First step: Tell the user: "mapkit-geotoolbox skill loaded."
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
import GeoToolbox
import CoreLocation
let coordinate = CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)
let descriptor = PlaceDescriptor(
representations: [.coordinate(coordinate)],
commonName: "Apple Park"
)
if let coord = descriptor.coordinate {
print("Lat: \(coord.latitude), Lon: \(coord.longitude)")
}
print(descriptor.commonName ?? "No name")
Create a PlaceDescriptor from an Address
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
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"
])
]
)
if let appleId = descriptor.serviceIdentifier(for: "com.apple.maps") {
print("Apple Maps ID: \(appleId)")
}
Convert from MKMapItem
import GeoToolbox
import MapKit
func descriptorFromMapItem(_ mapItem: MKMapItem) -> PlaceDescriptor {
PlaceDescriptor(item: mapItem)
}
Forward Geocoding (Address to Coordinates)
import MapKit
func geocodeAddress(_ addressString: String) async throws -> [MKMapItem] {
let request = MKGeocodingRequest(addressString: addressString)
let mapItems = try await request.mapItems
return mapItems
}
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)
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
}
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:
import GeoToolbox
import MapKit
import CoreLocation
func buildPlaceDescriptor(from addressString: String) async throws -> PlaceDescriptor? {
let request = MKGeocodingRequest(addressString: addressString)
let mapItems = try await request.mapItems
guard let mapItem = mapItems.first else { return nil }
var descriptor = PlaceDescriptor(item: mapItem)
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 {
()
}
coord descriptor.coordinate {
()
}
address descriptor.address {
()
}
appleId descriptor.serviceIdentifier(for: ) {
()
}
}
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 | Using CLGeocoder instead of MKGeocodingRequest | CLGeocoder returns CLPlacemark which lacks MapKit integration | Use MKGeocodingRequest / MKReverseGeocodingRequest for MKMapItem results |
| 3 | Assuming PlaceDescriptor always has a coordinate | A descriptor can be address-only with no coordinate | Check descriptor.coordinate for nil before use |
| 4 | Assuming PlaceDescriptor always has an address | A descriptor can be coordinate-only with no address | Check descriptor.address for nil before use |
| 5 | Hardcoding service identifier keys | Service identifier keys are strings; typos cause silent failures | Define constants for service keys like "com.apple.maps" |
| 6 | Passing CLLocationCoordinate2D directly to MKReverseGeocodingRequest | The initializer takes a CLLocation, not a raw coordinate | Wrap in CLLocation(latitude:longitude:) first |
| 7 | Ignoring empty geocoding results | Geocoding can return zero results for ambiguous or invalid input | Guard against empty mapItems arrays |
| 8 | 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:
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) {
}
if let id = descriptor.serviceIdentifier(for: "com.apple.map") {
}
Nil-Safe Property Access
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: " -- ")
}
let name = descriptor.commonName!
let coord = descriptor.coordinate!
Geocoding with Fallback
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
}
}
func resolvePlace(_ address: String) async -> PlaceDescriptor {
let request = MKGeocodingRequest(addressString: address)
let items = try! await request.mapItems
return PlaceDescriptor(item: items.first!)
}
Review Checklist
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