apple-corelocation
Apple CoreLocation Skill
Use this skill to write correct, modern CoreLocation code. CoreLocation provides services for geographic location, altitude, orientation, and proximity to iBeacons. It uses Wi-Fi, GPS, Bluetooth, magnetometer, barometer, and cellular hardware.
When to Read Reference Files
This SKILL.md contains the essential patterns and quick-reference API surface. For deeper implementation details, read the appropriate reference file:
| Topic | Reference File | When to Read |
|---|---|---|
| Live Updates & async/await patterns | references/live-updates.md |
SwiftUI apps, async location streams, background activity sessions |
| Authorization & Permissions | references/authorization.md |
Permission flows, Info.plist keys, authorization status handling |
| Region Monitoring & CLMonitor | references/monitoring.md |
Geofencing, condition monitoring, circular regions |
| Geocoding | references/geocoding.md |
Address ↔ coordinate conversion, reverse geocoding, CLPlacemark |
| iBeacon & Compass | references/beacon-compass.md |
Beacon ranging, heading updates, magnetometer |
| Background Location | references/background.md |
Background updates, CLBackgroundActivitySession, power optimization |
| CLLocationManager API | references/location-manager.md |
Full property/method reference for CLLocationManager |
Modern CoreLocation (iOS 17+): Prefer Async/Await
Since iOS 17, CoreLocation supports Swift concurrency. Prefer the modern async API over the legacy delegate-based approach for new projects.
Getting Live Location Updates (Recommended Pattern)
import CoreLocation
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
if let location = update.location {
// Process location
print("Lat: \(location.coordinate.latitude), Lon: \(location.coordinate.longitude)")
}
if update.authorizationDenied {
// Handle denied authorization
}
if update.authorizationRequestInProgress {
// System is showing the authorization dialog
}
}
The system automatically prompts for authorization when iteration begins if
status is .notDetermined. No explicit requestWhenInUseAuthorization() call
is needed with this pattern, but you may still call it for controlled timing.
Live Updates with Accuracy Configuration
// High accuracy (GPS, more power)
let updates = CLLocationUpdate.liveUpdates(.default)
// Power-efficient options
let updates = CLLocationUpdate.liveUpdates(.automotiveNavigation)
let updates = CLLocationUpdate.liveUpdates(.otherNavigation)
let updates = CLLocationUpdate.liveUpdates(.fitness)
let updates = CLLocationUpdate.liveUpdates(.airborne)
CLLocationUpdate Properties
location: CLLocation?— The location, or nil if unavailableisStationary: Bool— Whether the device is stationaryauthorizationDenied: Bool— Authorization was deniedauthorizationDeniedGlobally: Bool— Location services disabled system-wideauthorizationRequestInProgress: Bool— Auth dialog is being showninsufficientlyInUse: Bool— App lacks sufficient "in use" statelocationUnavailable: Bool— Location data temporarily unavailableaccuracyLimited: Bool— Accuracy authorization is reduced
SwiftUI Integration Pattern
@MainActor
class LocationsHandler: ObservableObject {
static let shared = LocationsHandler()
private let manager: CLLocationManager
private var background: CLBackgroundActivitySession?
@Published var lastLocation = CLLocation()
@Published var isStationary = false
@Published var updatesStarted: Bool = UserDefaults.standard.bool(forKey: "liveUpdatesStarted") {
didSet { UserDefaults.standard.set(updatesStarted, forKey: "liveUpdatesStarted") }
}
private init() {
self.manager = CLLocationManager()
}
func startLocationUpdates() {
if self.manager.authorizationStatus == .notDetermined {
self.manager.requestWhenInUseAuthorization()
}
Task {
do {
self.updatesStarted = true
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
if !self.updatesStarted { break }
if let loc = update.location {
self.lastLocation = loc
self.isStationary = update.isStationary
}
}
} catch {
print("Could not start location updates")
}
}
}
func stopLocationUpdates() {
self.updatesStarted = false
}
}
Authorization Quick Reference
Info.plist Keys (Required)
| Key | When to Use |
|---|---|
NSLocationWhenInUseUsageDescription |
App uses location while in foreground |
NSLocationAlwaysAndWhenInUseUsageDescription |
App needs location in background too |
NSLocationDefaultAccuracyReduced |
Request reduced accuracy by default |
Authorization Status Values (CLAuthorizationStatus)
| Value | Meaning |
|---|---|
.notDetermined |
User hasn't been asked yet |
.restricted |
App cannot use location (e.g., parental controls) |
.denied |
User explicitly denied |
.authorizedWhenInUse |
App can use location while in foreground |
.authorizedAlways |
App can use location at any time |
Requesting Authorization
let manager = CLLocationManager()
// For foreground-only access
manager.requestWhenInUseAuthorization()
// For background access (after getting "When In Use" first)
manager.requestAlwaysAuthorization()
// For temporary full accuracy (when user granted reduced accuracy)
manager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: "MyPurposeKey")
Condition Monitoring with CLMonitor (iOS 17+)
let monitor = await CLMonitor("myMonitor")
// Add a circular geographic condition
await monitor.add(
CLMonitor.CircularGeographicCondition(center: coordinate, radius: 200),
identifier: "coffee-shop"
)
// Observe events
for try await event in await monitor.events {
switch event.state {
case .satisfied:
print("Entered region: \(event.identifier)")
case .unsatisfied:
print("Exited region: \(event.identifier)")
default:
break
}
}
Geocoding Quick Reference
let geocoder = CLGeocoder()
// Reverse geocode: coordinate → address
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let placemark = placemarks?.first {
print(placemark.locality ?? "Unknown city")
}
}
// Forward geocode: address → coordinate
geocoder.geocodeAddressString("1 Apple Park Way, Cupertino") { placemarks, error in
if let location = placemarks?.first?.location {
print(location.coordinate)
}
}
CLLocation Key Properties
| Property | Type | Description |
|---|---|---|
coordinate |
CLLocationCoordinate2D |
Latitude and longitude (WGS 84) |
altitude |
CLLocationDistance |
Meters above sea level |
horizontalAccuracy |
CLLocationAccuracy |
Accuracy in meters (negative = invalid) |
verticalAccuracy |
CLLocationAccuracy |
Altitude accuracy in meters |
speed |
CLLocationSpeed |
Meters per second |
course |
CLLocationDirection |
Degrees relative to true north |
timestamp |
Date |
When the location was determined |
floor |
CLFloor? |
Floor of a building, if available |
sourceInformation |
CLLocationSourceInformation? |
Info about the location source |
Power Optimization Guidelines
Choose the most power-efficient service for your use case:
-
Visits service (
startMonitoringVisits()) — Most power-efficient. Reports places visited and time spent. Good for: check-in apps, travel logs. -
Significant-change service (
startMonitoringSignificantLocationChanges()) — Low power, uses Wi-Fi/cellular only. Good for: approximate location tracking. -
Standard location service (
startUpdatingLocation()) — Configurable accuracy viadesiredAccuracy. Good for: navigation, fitness tracking. -
Live updates (
CLLocationUpdate.liveUpdates()) — Modern async API with configurable activity types. Good for: any new project on iOS 17+.
Desired Accuracy Constants
| Constant | Description |
|---|---|
kCLLocationAccuracyBestForNavigation |
Highest precision, most power |
kCLLocationAccuracyBest |
Best available accuracy |
kCLLocationAccuracyNearestTenMeters |
Within ~10 meters |
kCLLocationAccuracyHundredMeters |
Within ~100 meters |
kCLLocationAccuracyKilometer |
Within ~1 km |
kCLLocationAccuracyThreeKilometers |
Within ~3 km |
kCLLocationAccuracyReduced |
Deliberately reduced accuracy |
Platform Considerations
- visionOS: Location services are limited. Background updates are not supported. Region monitoring methods do nothing for compatible iPad/iPhone apps running in visionOS.
- macOS: Apps are not suspended in background, so no special background capability needed.
- watchOS: Supports background location with capability. Use
CLBackgroundActivitySession. - Widgets: Check
isAuthorizedForWidgetUpdatesfor widget eligibility.
Common Pitfalls
- Always check
horizontalAccuracy— negative values mean the coordinate is invalid. - Do not assume location is immediately available after starting services.
- Handle authorization changes gracefully; users can revoke at any time via Settings.
- Geocoder requests are rate-limited; cache results and do not geocode on every location update.
- The system can pause location updates automatically. Set
pausesLocationUpdatesAutomaticallyandactivityTypeto help CoreLocation make good decisions. - For background updates on iOS: add the "Location updates" background mode capability
AND set
allowsBackgroundLocationUpdates = trueon the location manager.