Agent skill
geotoolbox
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.
Install this agent skill to your Project
npx add-skill https://github.com/rshankras/claude-code-apple-skills/tree/main/skills/mapkit/geotoolbox
SKILL.md
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"
)
// 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
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"
])
]
)
// Access a specific service identifier
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
}
// 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)
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:
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 | 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:
// 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
}
// 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
// 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: " -- ")
}
// Bad -- force-unwrapping optional properties
let name = descriptor.commonName! // crashes if nil
let coord = descriptor.coordinate! // crashes if no coordinate representation
Geocoding with Fallback
// 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
}
}
// 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 GeoToolboxis present when usingPlaceDescriptor,PlaceRepresentation, orSupportingPlaceRepresentation -
import MapKitis present when usingMKGeocodingRequest,MKReverseGeocodingRequest, orMKMapItem -
import CoreLocationis present when usingCLLocationCoordinate2DorCLLocation -
PlaceDescriptorproperties (coordinate,address,commonName) are checked fornilbefore use - Service identifier keys use defined constants, not raw string literals
-
MKReverseGeocodingRequestreceives aCLLocation, not a rawCLLocationCoordinate2D - Geocoding results are checked for empty arrays before accessing elements
- Geocoding calls use proper
async/awaiterror handling withdo/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
Mapview in SwiftUI
References
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
legal
Legal document generation and compliance guidance for indie Apple developers. Covers privacy policies, terms of service, EULAs, GDPR/CCPA/DPDP compliance, and Apple App Store legal requirements. Use when user needs legal documents or compliance guidance.
privacy-policy
Generate privacy policies, terms of service, and EULAs for Apple platform apps. Detects data collection patterns, third-party SDKs, and generates region-specific legal documents with Apple Privacy Nutrition Label mapping. Use when user needs legal documents or data collection disclosure for App Store submission.
ios-development
Comprehensive iOS development guidance including Swift best practices, SwiftUI patterns, UI/UX review against HIG, and app planning. Use for iOS code review, best practices, accessibility audits, or planning new iOS apps.
assistive-access
Assistive Access implementation for cognitive accessibility including simplified scenes, navigation icons, runtime detection, and design principles. Use when optimizing apps for Assistive Access mode.
ui-review
Review SwiftUI code for iOS/watchOS Human Interface Guidelines compliance, font usage, Dynamic Type support, and accessibility. Use when user mentions UI review, HIG, accessibility audit, font checks, or wants to verify interface design against Apple standards.
navigation-patterns
SwiftUI navigation architecture patterns including NavigationStack, NavigationSplitView, TabView, programmatic navigation, and custom transitions. Use when reviewing or building navigation, fixing navigation bugs, or architecting app flow.
Didn't find tool you were looking for?