agentsclimarketplace

Geotoolbox

Skill rshankras/claude-code-apple-skills/skills/mapkit/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.From its SKILL.md

Install
npx -y skills add rshankras/claude-code-apple-skills --skill geotoolbox

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

11.3 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it

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

APIMinimum OSImport
PlaceDescriptoriOS 26 / macOS 26GeoToolbox
PlaceRepresentationiOS 26 / macOS 26GeoToolbox
SupportingPlaceRepresentationiOS 26 / macOS 26GeoToolbox
MKGeocodingRequestiOS 26 / macOS 26MapKit
MKReverseGeocodingRequestiOS 26 / macOS 26MapKit
PlaceDescriptor(item:)iOS 26 / macOS 26GeoToolbox + MapKit
MKMapItemiOS 6 / macOS 10.9MapKit
CLLocationCoordinate2DiOS 2 / macOS 10.6CoreLocation

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

#MistakeProblemFix
1Importing only MapKit when using PlaceDescriptorPlaceDescriptor lives in GeoToolbox, not MapKitAdd import GeoToolbox alongside import MapKit
2Hardcoding service identifier keysService identifier keys are strings; typos cause silent failuresDefine constants for service keys like "com.apple.maps"
3Not handling geocoding errorsNetwork or service failures throw errorsUse 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 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

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.