Use for Core Location implementation patterns - authorization strategy, monitoring strategy, accuracy selection, background location
/plugin marketplace add CharlesWiltgen/Axiom/plugin install axiom@axiom-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Discipline skill for Core Location implementation decisions. Prevents common authorization mistakes, battery drain, and background location failures.
axiom-core-location-ref — API reference, code examplesaxiom-core-location-diag — Symptom-based troubleshootingaxiom-energy — Location as battery subsystemWrong (30-60% denial rate):
// First launch: "Can we have Always access?"
manager.requestAlwaysAuthorization()
Right (5-10% denial rate):
// Start with When In Use
CLServiceSession(authorization: .whenInUse)
// Later, when user triggers background feature:
CLServiceSession(authorization: .always)
Time cost: 15 min to fix code, but 30-60% of users permanently denied = feature adoption destroyed.
Why: Users deny aggressive requests. Start minimal, upgrade when user understands value.
Wrong (10x battery drain):
for try await update in CLLocationUpdate.liveUpdates() {
if isNearTarget(update.location) {
triggerGeofence()
}
}
Right (system-managed, low power):
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() }
}
Time cost: 5 min to refactor, saves 10x battery.
Wrong (wasted battery):
for try await update in CLLocationUpdate.liveUpdates() {
processLocation(update.location)
// Never stops, even when device stationary
}
Right (automatic pause/resume):
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)
}
}
Time cost: 2 min to add check, saves significant battery.
Wrong (broken UX):
for try await update in CLLocationUpdate.liveUpdates() {
guard let location = update.location else { continue }
// User denied - silent failure, no feedback
}
Right (graceful degradation):
for try await update in CLLocationUpdate.liveUpdates() {
if update.authorizationDenied {
showManualLocationPicker()
break
}
if update.authorizationDeniedGlobally {
showSystemLocationDisabledMessage()
break
}
if let location = update.location {
processLocation(location)
}
}
Time cost: 10 min to add handling, prevents confused users.
Wrong (battery drain for weather app):
// Weather app using navigation accuracy
CLLocationUpdate.liveUpdates(.automotiveNavigation)
Right (match accuracy to need):
// 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 | .automotiveNavigation | ~5m | Highest |
| Fitness tracking | .fitness | ~10m | High |
| Store finder | .default | ~10-100m | Medium |
| Weather | .default | ~100m+ | Low |
Time cost: 1 min to change, significant battery savings.
Wrong (battery drain, location icon persists):
func viewDidLoad() {
Task {
for try await update in CLLocationUpdate.liveUpdates() {
updateMap(update.location)
}
}
}
// User navigates away, updates continue forever
Right (cancel when done):
private 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
}
Time cost: 5 min to add cancellation, stops battery drain.
Wrong (procedural authorization juggling):
func requestAuth() {
switch manager.authorizationStatus {
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .authorizedWhenInUse:
if needsFullAccuracy {
manager.requestTemporaryFullAccuracyAuthorization(...)
}
// Complex state machine...
}
}
Right (declarative goals):
// 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() }
}
Time cost: 30 min to migrate, simpler code, fewer bugs.
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 geofence
Q1: 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 background
Q1: 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.
Context: PM says "Users want location reminders. Just request Always access on first launch so it works."
Pressure: Ship fast, seems simpler.
Reality:
Response:
"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."
Evidence: Apple's own guidance in WWDC 2024-10212: "CLServiceSessions should be taken proactively... hold one requiring full-accuracy when people engage a feature that would warrant a special ask for it."
Context: QA reports "App stops getting location when backgrounded."
Pressure: Quick fix before release.
Wrong fixes:
allowsBackgroundLocationUpdates = true without understandingRight diagnosis:
Response:
"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."
Checklist:
// 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()
}
Context: Geofences work in testing but not in production for some users.
Pressure: "It works on my device" dismissal.
Common causes:
Response:
"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?"
Diagnostic code:
// 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)")
}
}
}
}
Info.plist:
NSLocationWhenInUseUsageDescription with clear explanationNSLocationAlwaysAndWhenInUseUsageDescription if using Always (clear why background needed)NSLocationDefaultAccuracyReduced if reduced accuracy acceptableNSLocationTemporaryUsageDescriptionDictionary if requesting temporary full accuracyUIBackgroundModes includes location if background trackingAuthorization:
Updates:
Testing:
Setup:
Authorization:
Lifecycle:
Testing:
| 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 |
For iOS 14-16 support: Use CLLocationManager delegate pattern (see core-location-ref Part 7).
For iOS 17+: Prefer CLLocationUpdate and CLMonitor.
For iOS 18+: Add CLServiceSession for declarative authorization.
WWDC: 2023-10180, 2023-10147, 2024-10212
Docs: /corelocation, /corelocation/clmonitor, /corelocation/cllocationupdate, /corelocation/clservicesession
Skills: axiom-core-location-ref, axiom-core-location-diag, axiom-energy
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.