Claude-skill-registry axiom-core-location-ref
Use for Core Location API reference - CLLocationUpdate, CLMonitor, CLServiceSession, authorization, background location, geofencing
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/axiom-core-location-ref" ~/.claude/skills/majiayu000-claude-skill-registry-axiom-core-location-ref && rm -rf "$T"
skills/data/axiom-core-location-ref/SKILL.mdCore Location Reference
Comprehensive API reference for modern Core Location (iOS 17+).
When to Use
- Need API signatures for CLLocationUpdate, CLMonitor, CLServiceSession
- Implementing geofencing or region monitoring
- Configuring background location updates
- Understanding authorization patterns
- Debugging location service issues
Related Skills
— Anti-patterns, decision trees, pressure scenariosaxiom-core-location
— Symptom-based troubleshootingaxiom-core-location-diag
— Location as battery subsystem (accuracy vs power)axiom-energy-ref
Part 1: Modern API Overview (iOS 17+)
Four key classes replace legacy CLLocationManager patterns:
| Class | Purpose | iOS |
|---|---|---|
| AsyncSequence for location updates | 17+ |
| Condition-based geofencing/beacons | 17+ |
| Declarative authorization goals | 18+ |
| Background location support | 17+ |
Migration path: Legacy CLLocationManager still works, but new APIs provide:
- Swift concurrency (async/await)
- Automatic pause/resume
- Simplified authorization
- Better battery efficiency
Part 2: CLLocationUpdate API
Basic Usage
import CoreLocation Task { do { for try await update in CLLocationUpdate.liveUpdates() { if let location = update.location { // Process location } if update.isStationary { break // Stop when user stops moving } } } catch { // Handle location errors } }
LiveConfiguration Options
CLLocationUpdate.liveUpdates(.default) CLLocationUpdate.liveUpdates(.automotiveNavigation) CLLocationUpdate.liveUpdates(.otherNavigation) CLLocationUpdate.liveUpdates(.fitness) CLLocationUpdate.liveUpdates(.airborne)
Choose based on use case. If unsure, use
.default or omit parameter.
Key Properties
| Property | Type | Description |
|---|---|---|
| | Current location (nil if unavailable) |
| | True when device stopped moving |
| | User denied location access |
| | Location services disabled system-wide |
| | Awaiting user authorization decision |
| | Reduced accuracy (updates every 15-20 min) |
| | Cannot determine location |
| | Can't request auth (not in foreground) |
Automatic Pause/Resume
When device becomes stationary:
- Final update delivered with
and validisStationary = truelocation - Updates pause (saves battery)
- When device moves, updates resume with
isStationary = false
No action required—happens automatically.
AsyncSequence Operations
// Get first location with speed > 10 m/s let fastUpdate = try await CLLocationUpdate.liveUpdates() .first { $0.location?.speed ?? 0 > 10 } // WARNING: Avoid filters that may never match (e.g., horizontalAccuracy < 1)
Part 3: CLMonitor API
Swift actor for monitoring geographic conditions and beacons.
Basic Geofencing
let monitor = await CLMonitor("MyMonitor") // Add circular region let condition = CLMonitor.CircularGeographicCondition( center: CLLocationCoordinate2D(latitude: 37.33, longitude: -122.01), radius: 100 ) await monitor.add(condition, identifier: "ApplePark") // Await events for try await event in monitor.events { switch event.state { case .satisfied: // User entered region handleEntry(event.identifier) case .unsatisfied: // User exited region handleExit(event.identifier) case .unknown: break @unknown default: break } }
CircularGeographicCondition
CLMonitor.CircularGeographicCondition( center: CLLocationCoordinate2D, radius: CLLocationDistance // meters, minimum ~100m effective )
BeaconIdentityCondition
Three granularity levels:
// All beacons with UUID (any site) CLMonitor.BeaconIdentityCondition(uuid: myUUID) // Specific site (UUID + major) CLMonitor.BeaconIdentityCondition(uuid: myUUID, major: 100) // Specific beacon (UUID + major + minor) CLMonitor.BeaconIdentityCondition(uuid: myUUID, major: 100, minor: 5)
Condition Limit
Maximum 20 conditions per app. Prioritize what to monitor. Swap regions dynamically based on user location if needed.
Adding with Assumed State
// If you know initial state await monitor.add(condition, identifier: "Work", assuming: .unsatisfied)
Core Location will correct if assumption wrong.
Accessing Records
// Get single record if let record = await monitor.record(for: "ApplePark") { let condition = record.condition let lastEvent = record.lastEvent let state = lastEvent.state let date = lastEvent.date } // Get all identifiers let allIds = await monitor.identifiers
Event Properties
| Property | Description |
|---|---|
| String identifier of condition |
| , , |
| When state changed |
| For wildcard beacons, actual UUID/major/minor detected |
| Too many conditions (max 20) |
| Condition type not available |
| Reduced accuracy prevents monitoring |
Critical Requirements
- One monitor per name — Only one instance with given name at a time
- Always await events — Events only become
after handlinglastEvent - Reinitialize on launch — Recreate monitor in
didFinishLaunchingWithOptions
Part 4: CLServiceSession API (iOS 18+)
Declarative authorization—tell Core Location what you need, not what to do.
Basic Usage
// Hold session for duration of feature let session = CLServiceSession(authorization: .whenInUse) for try await update in CLLocationUpdate.liveUpdates() { // Process updates }
Authorization Requirements
CLServiceSession(authorization: .none) // No auth request CLServiceSession(authorization: .whenInUse) // Request When In Use CLServiceSession(authorization: .always) // Request Always (must start in foreground)
Full Accuracy Request
// For features requiring precise location (e.g., navigation) CLServiceSession( authorization: .whenInUse, fullAccuracyPurposeKey: "NavigationPurpose" // Key in Info.plist )
Requires
NSLocationTemporaryUsageDescriptionDictionary in Info.plist.
Implicit Sessions
Iterating
CLLocationUpdate.liveUpdates() or CLMonitor.events creates implicit session with .whenInUse goal.
To disable implicit sessions:
<!-- Info.plist --> <key>NSLocationRequireExplicitServiceSession</key> <true/>
Session Layering
Don't replace sessions—layer them:
// Base session for app let baseSession = CLServiceSession(authorization: .whenInUse) // Additional session when navigation feature active let navSession = CLServiceSession( authorization: .whenInUse, fullAccuracyPurposeKey: "Nav" ) // Both sessions active simultaneously
Diagnostic Properties
for try await diagnostic in session.diagnostics { if diagnostic.authorizationDenied { // User denied—offer alternative } if diagnostic.authorizationDeniedGlobally { // Location services off system-wide } if diagnostic.insufficientlyInUse { // Can't request auth (not foreground) } if diagnostic.alwaysAuthorizationDenied { // Always auth specifically denied } if !diagnostic.authorizationRequestInProgress { // Decision made (granted or denied) break } }
Session Lifecycle
Sessions persist through:
- App backgrounding
- App suspension
- App termination (Core Location tracks)
On relaunch, recreate sessions immediately in
didFinishLaunchingWithOptions.
Part 5: Authorization State Machine
Authorization Levels
| Status | Description |
|---|---|
| User hasn't decided |
| Parental controls prevent access |
| User explicitly refused |
| Access while app active |
| Background access |
Accuracy Authorization
| Value | Description |
|---|---|
| Precise location |
| Approximate (~5km), updates every 15-20 min |
Required Info.plist Keys
<!-- Required for When In Use --> <key>NSLocationWhenInUseUsageDescription</key> <string>We need your location to show nearby places</string> <!-- Required for Always --> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We track your location to send arrival reminders</string> <!-- Optional: default to reduced accuracy --> <key>NSLocationDefaultAccuracyReduced</key> <true/>
Legacy Authorization Pattern
@MainActor class LocationManager: NSObject, CLLocationManagerDelegate { private let manager = CLLocationManager() func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { switch manager.authorizationStatus { case .notDetermined: manager.requestWhenInUseAuthorization() case .authorizedWhenInUse, .authorizedAlways: enableLocationFeatures() case .denied, .restricted: disableLocationFeatures() @unknown default: break } } }
Part 6: Background Location
Requirements
- Background mode capability: Signing & Capabilities → Background Modes → Location updates
- Info.plist: Adds
withUIBackgroundModes
valuelocation - CLBackgroundActivitySession or LiveActivity
CLBackgroundActivitySession
// Create and HOLD reference (deallocation invalidates session) var backgroundSession: CLBackgroundActivitySession? func startBackgroundTracking() { // Must start from foreground backgroundSession = CLBackgroundActivitySession() Task { for try await update in CLLocationUpdate.liveUpdates() { processUpdate(update) } } } func stopBackgroundTracking() { backgroundSession?.invalidate() backgroundSession = nil }
Background Indicator
Blue status bar/pill appears when:
- App authorized as "When In Use"
- App receiving location in background
- CLBackgroundActivitySession active
App Lifecycle
- Foreground → Background: Session continues
- Background → Suspended: Session preserved, updates pause
- Suspended → Terminated: Core Location tracks session
- Terminated → Background launch: Recreate session immediately
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Recreate background session if was tracking if wasTrackingLocation { backgroundSession = CLBackgroundActivitySession() startLocationUpdates() } return true }
Part 7: Legacy APIs (iOS 12-16)
CLLocationManager Delegate Pattern
class LocationManager: NSObject, CLLocationManagerDelegate { private let manager = CLLocationManager() override init() { super.init() manager.delegate = self manager.desiredAccuracy = kCLLocationAccuracyBest manager.distanceFilter = 10 // meters } func startUpdates() { manager.startUpdatingLocation() } func stopUpdates() { manager.stopUpdatingLocation() } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let location = locations.last else { return } // Process location } }
Accuracy Constants
| Constant | Accuracy | Battery Impact |
|---|---|---|
| ~5m | Highest |
| ~10m | Very High |
| ~10m | High |
| ~100m | Medium |
| ~1km | Low |
| ~3km | Very Low |
| ~5km | Lowest |
Legacy Region Monitoring
// Deprecated in iOS 17, use CLMonitor instead let region = CLCircularRegion( center: coordinate, radius: 100, identifier: "MyRegion" ) region.notifyOnEntry = true region.notifyOnExit = true manager.startMonitoring(for: region)
Significant Location Changes
Low-power alternative for coarse tracking:
manager.startMonitoringSignificantLocationChanges() // Updates ~500m movements, works in background
Visit Monitoring
Detect arrivals/departures:
manager.startMonitoringVisits() func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) { let arrival = visit.arrivalDate let departure = visit.departureDate let coordinate = visit.coordinate }
Part 8: Geofencing Best Practices
Region Size
- Minimum effective radius: ~100 meters
- Smaller regions: May not trigger reliably
- Larger regions: More reliable but less precise
20-Region Limit Strategy
// Dynamic region management func updateMonitoredRegions(userLocation: CLLocation) async { let nearbyPOIs = fetchNearbyPOIs(around: userLocation, limit: 20) // Remove old regions for id in await monitor.identifiers { if !nearbyPOIs.contains(where: { $0.id == id }) { await monitor.remove(id) } } // Add new regions for poi in nearbyPOIs { let condition = CLMonitor.CircularGeographicCondition( center: poi.coordinate, radius: 100 ) await monitor.add(condition, identifier: poi.id) } }
Entry/Exit Timing
- Entry: Usually within seconds to minutes
- Exit: May take 3-5 minutes after leaving
- Accuracy depends on: Cell towers, WiFi, GPS availability
Persistence
- Conditions persist across app launches
- Must reinitialize monitor with same name on launch
- Core Location wakes app for events
Part 9: Testing and Simulation
Xcode Location Simulation
- Run on simulator
- Debug → Simulate Location → Choose location
- Or use custom GPX file
Custom GPX Route
<?xml version="1.0"?> <gpx version="1.1"> <wpt lat="37.331686" lon="-122.030656"> <time>2024-01-01T00:00:00Z</time> </wpt> <wpt lat="37.332686" lon="-122.031656"> <time>2024-01-01T00:00:10Z</time> </wpt> </gpx>
Testing Authorization States
Settings → Privacy & Security → Location Services:
- Toggle app authorization
- Toggle system-wide location services
- Test reduced accuracy
Console Filtering
# Filter location logs log stream --predicate 'subsystem == "com.apple.locationd"'
Part 10: Swift Concurrency Integration
Task Cancellation
let locationTask = Task { for try await update in CLLocationUpdate.liveUpdates() { if Task.isCancelled { break } processUpdate(update) } } // Later locationTask.cancel()
MainActor Considerations
@MainActor class LocationViewModel: ObservableObject { @Published var currentLocation: CLLocation? func startTracking() { Task { for try await update in CLLocationUpdate.liveUpdates() { // Already on MainActor, safe to update @Published self.currentLocation = update.location } } } }
Error Handling
Task { do { for try await update in CLLocationUpdate.liveUpdates() { if update.authorizationDenied { throw LocationError.authorizationDenied } processUpdate(update) } } catch { handleError(error) } }
Troubleshooting Quick Reference
| Symptom | Check |
|---|---|
| No location updates | Authorization status, Info.plist keys |
| Background not working | Background mode capability, CLBackgroundActivitySession |
| Always auth not effective | CLServiceSession with , started in foreground |
| Geofence not triggering | Region count (max 20), radius (min ~100m) |
| Reduced accuracy only | Check , request temporary full accuracy |
| Location icon stays on | Ensure or break from async loop |
Resources
WWDC: 2023-10180, 2023-10147, 2024-10212
Docs: /corelocation, /corelocation/clmonitor, /corelocation/cllocationupdate, /corelocation/clservicesession
Skills: axiom-core-location, axiom-core-location-diag, axiom-energy-ref