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