axiom-core-location-ref

Core Location Reference

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "axiom-core-location-ref" with this command: npx skills add charleswiltgen/axiom/charleswiltgen-axiom-axiom-core-location-ref

Core 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

  • axiom-core-location — Anti-patterns, decision trees, pressure scenarios

  • axiom-core-location-diag — Symptom-based troubleshooting

  • axiom-energy-ref — Location as battery subsystem (accuracy vs power)

Part 1: Modern API Overview (iOS 17+)

Four key classes replace legacy CLLocationManager patterns:

Class Purpose iOS

CLLocationUpdate

AsyncSequence for location updates 17+

CLMonitor

Condition-based geofencing/beacons 17+

CLServiceSession

Declarative authorization goals 18+

CLBackgroundActivitySession

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

location

CLLocation?

Current location (nil if unavailable)

isStationary

Bool

True when device stopped moving

authorizationDenied

Bool

User denied location access

authorizationDeniedGlobally

Bool

Location services disabled system-wide

authorizationRequestInProgress

Bool

Awaiting user authorization decision

accuracyLimited

Bool

Reduced accuracy (updates every 15-20 min)

locationUnavailable

Bool

Cannot determine location

insufficientlyInUse

Bool

Can't request auth (not in foreground)

Automatic Pause/Resume

When device becomes stationary:

  • Final update delivered with isStationary = true and valid location

  • 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

identifier

String identifier of condition

state

.satisfied , .unsatisfied , .unknown

date

When state changed

refinement

For wildcard beacons, actual UUID/major/minor detected

conditionLimitExceeded

Too many conditions (max 20)

conditionUnsupported

Condition type not available

accuracyLimited

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 lastEvent after handling

  • 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

.notDetermined

User hasn't decided

.restricted

Parental controls prevent access

.denied

User explicitly refused

.authorizedWhenInUse

Access while app active

.authorizedAlways

Background access

Accuracy Authorization

Value Description

.fullAccuracy

Precise location

.reducedAccuracy

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 UIBackgroundModes with location value

  • 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

kCLLocationAccuracyBestForNavigation

~5m Highest

kCLLocationAccuracyBest

~10m Very High

kCLLocationAccuracyNearestTenMeters

~10m High

kCLLocationAccuracyHundredMeters

~100m Medium

kCLLocationAccuracyKilometer

~1km Low

kCLLocationAccuracyThreeKilometers

~3km Very Low

kCLLocationAccuracyReduced

~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) } }

Part 11: Geocoding

CLGeocoder — Forward Geocoding (Address → Coordinate)

let geocoder = CLGeocoder()

func geocodeAddress(_ address: String) async throws -> CLLocation? { let placemarks = try await geocoder.geocodeAddressString(address) return placemarks.first?.location }

// With locale for localized results let placemarks = try await geocoder.geocodeAddressString( "1 Apple Park Way", in: nil, // CLRegion hint (optional) preferredLocale: Locale(identifier: "en_US") )

CLGeocoder — Reverse Geocoding (Coordinate → Address)

func reverseGeocode(_ location: CLLocation) async throws -> CLPlacemark? { let placemarks = try await geocoder.reverseGeocodeLocation(location) return placemarks.first }

// Usage if let placemark = try await reverseGeocode(location) { let street = placemark.thoroughfare // "Apple Park Way" let city = placemark.locality // "Cupertino" let state = placemark.administrativeArea // "CA" let zip = placemark.postalCode // "95014" let country = placemark.country // "United States" let isoCountry = placemark.isoCountryCode // "US" }

CLPlacemark Key Properties

Property Example Notes

name

"Apple Park" Location name

thoroughfare

"Apple Park Way" Street name

subThoroughfare

"1" Street number

locality

"Cupertino" City

subLocality

"Silicon Valley" Neighborhood

administrativeArea

"CA" State/province

postalCode

"95014" ZIP/postal code

country

"United States" Country name

isoCountryCode

"US" ISO country code

timeZone

America/Los_Angeles Time zone

location

CLLocation Coordinate

Geocoding Rate Limits

  • One request at a time — CLGeocoder throws if a request is in progress

  • Apple rate-limits — Throttle to avoid kCLErrorGeocodeCanceled

  • Cache results — Don't re-geocode the same address/coordinate

  • Batch carefully — Add delays between sequential geocode requests

// Check if geocoder is busy if geocoder.isGeocoding { geocoder.cancelGeocode() // Cancel previous before starting new }

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 .always , started in foreground

Geofence not triggering Region count (max 20), radius (min ~100m)

Reduced accuracy only Check accuracyAuthorization , request temporary full accuracy

Location icon stays on Ensure stopUpdatingLocation() 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

axiom-vision

No summary provided by upstream source.

Repository SourceNeeds Review
General

axiom-swiftdata

No summary provided by upstream source.

Repository SourceNeeds Review
General

axiom-swiftui-26-ref

No summary provided by upstream source.

Repository SourceNeeds Review
General

axiom-swiftui-architecture

No summary provided by upstream source.

Repository SourceNeeds Review