skills/mapkit/geotoolbox/SKILL.md
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.
npx skillsauth add rshankras/claude-code-apple-skills geotoolboxInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
Use this skill when the user:
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 | 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 |
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")
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)")
}
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)")
}
import GeoToolbox
import MapKit
func descriptorFromMapItem(_ mapItem: MKMapItem) -> PlaceDescriptor {
PlaceDescriptor(item: mapItem)
}
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)")
}
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")")
}
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)")
}
}
| # | 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 |
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
}
// 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
// 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
}
import GeoToolbox is present when using PlaceDescriptor, PlaceRepresentation, or SupportingPlaceRepresentationimport MapKit is present when using MKGeocodingRequest, MKReverseGeocodingRequest, or MKMapItemimport CoreLocation is present when using CLLocationCoordinate2D or CLLocationPlaceDescriptor properties (coordinate, address, commonName) are checked for nil before useMKReverseGeocodingRequest receives a CLLocation, not a raw CLLocationCoordinate2Dasync/await error handling with do/catchMap view in SwiftUIdevelopment
Build, install, and launch an iOS app on a physical iPhone or iPad entirely from the command line (no Xcode GUI), using xcodebuild + devicectl. Use when the user wants to run, test, or screenshot their app on a real device without opening Xcode.
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.
development
Build, install, launch, and screenshot an iOS app in the Simulator to verify a change visually. Use when the user wants to run the app, see a change live, screenshot the running app, or confirm a UI fix actually works (not just that it compiles).
development
Audits skills in this repo for consistency, API drift, and structural gaps. Produces a prioritized report grouped by severity (Critical/High/Medium/Low). Use when asked to "audit skills", "check the skill repo for drift", or when planning bulk skill cleanup. Read-only — does not apply fixes.