Loading...
Loading...
Use for Core Location implementation patterns - authorization strategy, monitoring strategy, accuracy selection, background location
npx skill4agent add charleswiltgen/axiom axiom-core-locationaxiom-core-location-refaxiom-core-location-diagaxiom-energy// First launch: "Can we have Always access?"
manager.requestAlwaysAuthorization()// Start with When In Use
CLServiceSession(authorization: .whenInUse)
// Later, when user triggers background feature:
CLServiceSession(authorization: .always)for try await update in CLLocationUpdate.liveUpdates() {
if isNearTarget(update.location) {
triggerGeofence()
}
}let monitor = await CLMonitor("Geofences")
let condition = CLMonitor.CircularGeographicCondition(
center: target, radius: 100
)
await monitor.add(condition, identifier: "Target")
for try await event in monitor.events {
if event.state == .satisfied { triggerGeofence() }
}for try await update in CLLocationUpdate.liveUpdates() {
processLocation(update.location)
// Never stops, even when device stationary
}for try await update in CLLocationUpdate.liveUpdates() {
if let location = update.location {
processLocation(location)
}
if update.isStationary, let location = update.location {
// Device stopped moving - updates pause automatically
// Will resume when device moves again
saveLastKnownLocation(location)
}
}for try await update in CLLocationUpdate.liveUpdates() {
guard let location = update.location else { continue }
// User denied - silent failure, no feedback
}for try await update in CLLocationUpdate.liveUpdates() {
if update.authorizationDenied {
showManualLocationPicker()
break
}
if update.authorizationDeniedGlobally {
showSystemLocationDisabledMessage()
break
}
if let location = update.location {
processLocation(location)
}
}// Weather app using navigation accuracy
CLLocationUpdate.liveUpdates(.automotiveNavigation)// Weather: city-level is fine
CLLocationUpdate.liveUpdates(.default) // or .fitness for runners
// Navigation: needs high accuracy
CLLocationUpdate.liveUpdates(.automotiveNavigation)| Use Case | Configuration | Accuracy | Battery |
|---|---|---|---|
| Navigation | | ~5m | Highest |
| Fitness tracking | | ~10m | High |
| Store finder | | ~10-100m | Medium |
| Weather | | ~100m+ | Low |
func viewDidLoad() {
Task {
for try await update in CLLocationUpdate.liveUpdates() {
updateMap(update.location)
}
}
}
// User navigates away, updates continue foreverprivate var locationTask: Task<Void, Error>?
func startTracking() {
locationTask = Task {
for try await update in CLLocationUpdate.liveUpdates() {
if Task.isCancelled { break }
updateMap(update.location)
}
}
}
func stopTracking() {
locationTask?.cancel()
locationTask = nil
}func requestAuth() {
switch manager.authorizationStatus {
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .authorizedWhenInUse:
if needsFullAccuracy {
manager.requestTemporaryFullAccuracyAuthorization(...)
}
// Complex state machine...
}
}// Just declare what you need - Core Location handles the rest
let session = CLServiceSession(authorization: .whenInUse)
// For feature needing full accuracy
let navSession = CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "Navigation"
)
// Monitor diagnostics if needed
for try await diag in session.diagnostics {
if diag.authorizationDenied { handleDenial() }
}Q1: Does your feature REQUIRE background location?
├─ NO → Use .whenInUse
│ └─ Q2: Does any feature need precise location?
│ ├─ ALWAYS → Add fullAccuracyPurposeKey to session
│ └─ SOMETIMES → Layer full-accuracy session when feature active
│
└─ YES → Start with .whenInUse, upgrade to .always when user triggers feature
└─ Q3: When does user first need background location?
├─ IMMEDIATELY (e.g., fitness tracker) → Request .always on first relevant action
└─ LATER (e.g., geofence reminders) → Add .always session when user creates first geofenceQ1: What are you monitoring for?
├─ USER POSITION (continuous tracking)
│ └─ Use CLLocationUpdate.liveUpdates()
│ └─ Q2: What activity?
│ ├─ Driving navigation → .automotiveNavigation
│ ├─ Walking/cycling nav → .otherNavigation
│ ├─ Fitness tracking → .fitness
│ ├─ Airplane apps → .airborne
│ └─ General → .default or omit
│
├─ ENTRY/EXIT REGIONS (geofencing)
│ └─ Use CLMonitor with CircularGeographicCondition
│ └─ Note: Maximum 20 conditions per app
│
├─ BEACON PROXIMITY
│ └─ Use CLMonitor with BeaconIdentityCondition
│ └─ Choose granularity: UUID only, UUID+major, UUID+major+minor
│
└─ SIGNIFICANT CHANGES ONLY (lowest power)
└─ Use startMonitoringSignificantLocationChanges() (legacy)
└─ Updates ~500m movements, works in backgroundQ1: What's the minimum accuracy that makes your feature work?
├─ TURN-BY-TURN NAV needs 5-10m → .automotiveNavigation / .otherNavigation
├─ FITNESS TRACKING needs 10-20m → .fitness
├─ STORE FINDER needs 100m → .default
├─ WEATHER/CITY needs 1km+ → .default (reduced accuracy acceptable)
└─ GEOFENCING uses system determination → CLMonitor handles it
Q2: Will user be moving fast?
├─ DRIVING (high speed) → .automotiveNavigation (extra processing for speed)
├─ CYCLING/WALKING → .otherNavigation
└─ STATIONARY/SLOW → .default
Always start with lowest acceptable accuracy. Higher accuracy = higher battery drain."Always authorization has 30-60% denial rates when requested upfront. We should start with When In Use, then request Always upgrade when the user creates their first location reminder. This gives us a 5-10% denial rate because users understand why they need it."
allowsBackgroundLocationUpdates = true"Background location requires specific setup. Let me check: (1) Background mode capability, (2) CLBackgroundActivitySession held during tracking, (3) session started from foreground. Missing any of these causes silent failure."
// 1. Signing & Capabilities → Background Modes → Location updates
// 2. Hold session reference (property, not local variable)
var backgroundSession: CLBackgroundActivitySession?
func startBackgroundTracking() {
// 3. Must start from foreground
backgroundSession = CLBackgroundActivitySession()
startLocationUpdates()
}"Geofencing has several system constraints. Check: (1) Are we within the 20-condition limit? (2) Are all radii at least 100m? (3) Is the app reinitializing CLMonitor on launch? (4) Is the app always awaiting on monitor.events?"
// Check condition count
let count = await monitor.identifiers.count
if count >= 20 {
print("At 20-condition limit!")
}
// Check all conditions
for id in await monitor.identifiers {
if let record = await monitor.record(for: id) {
let condition = record.condition
if let geo = condition as? CLMonitor.CircularGeographicCondition {
if geo.radius < 100 {
print("Radius too small: \(id)")
}
}
}
}NSLocationWhenInUseUsageDescriptionNSLocationAlwaysAndWhenInUseUsageDescriptionNSLocationDefaultAccuracyReducedNSLocationTemporaryUsageDescriptionDictionaryUIBackgroundModeslocation| Feature | iOS Version | Notes |
|---|---|---|
| CLLocationUpdate | iOS 17+ | AsyncSequence API |
| CLMonitor | iOS 17+ | Replaces CLCircularRegion |
| CLBackgroundActivitySession | iOS 17+ | Background with blue indicator |
| CLServiceSession | iOS 18+ | Declarative authorization |
| Implicit service sessions | iOS 18+ | From iterating liveUpdates |
| CLLocationManager | iOS 2+ | Legacy but still works |