axiom-core-location-ref by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-core-location-ref现代 Core Location(iOS 17+)的全面 API 参考。
axiom-core-location — 反模式、决策树、压力场景axiom-core-location-diag — 基于症状的故障排除axiom-energy-ref — 作为电池子系统的定位(精度与功耗)四个关键类取代了传统的 CLLocationManager 模式:
| 类 | 用途 | iOS |
|---|---|---|
CLLocationUpdate |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 位置更新的 AsyncSequence |
| 17+ |
CLMonitor | 基于条件的地理围栏/信标监控 | 17+ |
CLServiceSession | 声明式授权目标 | 18+ |
CLBackgroundActivitySession | 后台定位支持 | 17+ |
迁移路径:传统的 CLLocationManager 仍然有效,但新 API 提供了:
import CoreLocation
Task {
do {
for try await update in CLLocationUpdate.liveUpdates() {
if let location = update.location {
// 处理位置
}
if update.isStationary {
break // 当用户停止移动时停止
}
}
} catch {
// 处理定位错误
}
}
CLLocationUpdate.liveUpdates(.default)
CLLocationUpdate.liveUpdates(.automotiveNavigation)
CLLocationUpdate.liveUpdates(.otherNavigation)
CLLocationUpdate.liveUpdates(.fitness)
CLLocationUpdate.liveUpdates(.airborne)
根据使用场景选择。如果不确定,使用 .default 或省略参数。
| 属性 | 类型 | 描述 |
|---|---|---|
location | CLLocation? | 当前位置(如果不可用则为 nil) |
isStationary | Bool | 设备停止移动时为 true |
authorizationDenied | Bool | 用户拒绝位置访问 |
authorizationDeniedGlobally | Bool | 系统范围内定位服务被禁用 |
authorizationRequestInProgress | Bool | 等待用户授权决定 |
accuracyLimited | Bool | 精度降低(每 15-20 分钟更新一次) |
locationUnavailable | Bool | 无法确定位置 |
insufficientlyInUse | Bool | 无法请求授权(不在前台) |
当设备变为静止时:
isStationary = true 且 location 有效isStationary = false无需任何操作——自动发生。
// 获取速度 > 10 米/秒的第一个位置
let fastUpdate = try await CLLocationUpdate.liveUpdates()
.first { $0.location?.speed ?? 0 > 10 }
// 警告:避免使用可能永远无法匹配的过滤器(例如,horizontalAccuracy < 1)
用于监控地理条件和信标的 Swift actor。
let monitor = await CLMonitor("MyMonitor")
// 添加圆形区域
let condition = CLMonitor.CircularGeographicCondition(
center: CLLocationCoordinate2D(latitude: 37.33, longitude: -122.01),
radius: 100
)
await monitor.add(condition, identifier: "ApplePark")
// 等待事件
for try await event in monitor.events {
switch event.state {
case .satisfied: // 用户进入区域
handleEntry(event.identifier)
case .unsatisfied: // 用户离开区域
handleExit(event.identifier)
case .unknown:
break
@unknown default:
break
}
}
CLMonitor.CircularGeographicCondition(
center: CLLocationCoordinate2D,
radius: CLLocationDistance // 米,最小有效半径约 100 米
)
三个粒度级别:
// 所有具有 UUID 的信标(任何站点)
CLMonitor.BeaconIdentityCondition(uuid: myUUID)
// 特定站点(UUID + major)
CLMonitor.BeaconIdentityCondition(uuid: myUUID, major: 100)
// 特定信标(UUID + major + minor)
CLMonitor.BeaconIdentityCondition(uuid: myUUID, major: 100, minor: 5)
每个应用最多 20 个条件。 优先考虑要监控的内容。如果需要,可以根据用户位置动态交换区域。
// 如果你知道初始状态
await monitor.add(condition, identifier: "Work", assuming: .unsatisfied)
如果假设错误,Core Location 会进行纠正。
// 获取单个记录
if let record = await monitor.record(for: "ApplePark") {
let condition = record.condition
let lastEvent = record.lastEvent
let state = lastEvent.state
let date = lastEvent.date
}
// 获取所有标识符
let allIds = await monitor.identifiers
| 属性 | 描述 |
|---|---|
identifier | 条件的字符串标识符 |
state | .satisfied、.unsatisfied、.unknown |
date | 状态改变的时间 |
refinement | 对于通配符信标,检测到的实际 UUID/major/minor |
conditionLimitExceeded | 条件过多(最多 20 个) |
conditionUnsupported | 条件类型不可用 |
accuracyLimited | 精度降低导致无法监控 |
lastEventdidFinishLaunchingWithOptions 中重新创建监视器声明式授权——告诉 Core Location 你需要什么,而不是要做什么。
// 在功能持续期间保持会话
let session = CLServiceSession(authorization: .whenInUse)
for try await update in CLLocationUpdate.liveUpdates() {
// 处理更新
}
CLServiceSession(authorization: .none) // 不请求授权
CLServiceSession(authorization: .whenInUse) // 请求使用时授权
CLServiceSession(authorization: .always) // 请求始终授权(必须在前台启动)
// 对于需要精确定位的功能(例如导航)
CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "NavigationPurpose" // Info.plist 中的键
)
需要在 Info.plist 中配置 NSLocationTemporaryUsageDescriptionDictionary。
遍历 CLLocationUpdate.liveUpdates() 或 CLMonitor.events 会创建带有 .whenInUse 目标的隐式会话。
要禁用隐式会话:
<!-- Info.plist -->
<key>NSLocationRequireExplicitServiceSession</key>
<true/>
不要替换会话——将它们分层:
// 应用的基础会话
let baseSession = CLServiceSession(authorization: .whenInUse)
// 导航功能激活时的附加会话
let navSession = CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "Nav"
)
// 两个会话同时处于活动状态
for try await diagnostic in session.diagnostics {
if diagnostic.authorizationDenied {
// 用户拒绝——提供替代方案
}
if diagnostic.authorizationDeniedGlobally {
// 系统范围内定位服务关闭
}
if diagnostic.insufficientlyInUse {
// 无法请求授权(不在前台)
}
if diagnostic.alwaysAuthorizationDenied {
// 始终授权被明确拒绝
}
if !diagnostic.authorizationRequestInProgress {
// 已做出决定(授予或拒绝)
break
}
}
会话在以下情况下持续存在:
重新启动时,立即在 didFinishLaunchingWithOptions 中重新创建会话。
| 状态 | 描述 |
|---|---|
.notDetermined | 用户尚未决定 |
.restricted | 家长控制阻止访问 |
.denied | 用户明确拒绝 |
.authorizedWhenInUse | 应用活动时可访问 |
.authorizedAlways | 后台访问 |
| 值 | 描述 |
|---|---|
.fullAccuracy | 精确定位 |
.reducedAccuracy | 近似定位(约 5 公里),每 15-20 分钟更新一次 |
<!-- 使用时授权必需 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>我们需要您的位置来显示附近的地点</string>
<!-- 始终授权必需 -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>我们跟踪您的位置以发送到达提醒</string>
<!-- 可选:默认使用降低的精度 -->
<key>NSLocationDefaultAccuracyReduced</key>
<true/>
@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
}
}
}
location 值的 UIBackgroundModes// 创建并 HOLD 引用(释放会使会话失效)
var backgroundSession: CLBackgroundActivitySession?
func startBackgroundTracking() {
// 必须从前台启动
backgroundSession = CLBackgroundActivitySession()
Task {
for try await update in CLLocationUpdate.liveUpdates() {
processUpdate(update)
}
}
}
func stopBackgroundTracking() {
backgroundSession?.invalidate()
backgroundSession = nil
}
当出现以下情况时,会出现蓝色状态栏/药丸:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 如果之前正在跟踪位置,则重新创建后台会话
if wasTrackingLocation {
backgroundSession = CLBackgroundActivitySession()
startLocationUpdates()
}
return true
}
class LocationManager: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.distanceFilter = 10 // 米
}
func startUpdates() {
manager.startUpdatingLocation()
}
func stopUpdates() {
manager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
// 处理位置
}
}
| 常量 | 精度 | 电池影响 |
|---|---|---|
kCLLocationAccuracyBestForNavigation | ~5米 | 最高 |
kCLLocationAccuracyBest | ~10米 | 非常高 |
kCLLocationAccuracyNearestTenMeters | ~10米 | 高 |
kCLLocationAccuracyHundredMeters | ~100米 | 中等 |
kCLLocationAccuracyKilometer | ~1公里 | 低 |
kCLLocationAccuracyThreeKilometers | ~3公里 | 非常低 |
kCLLocationAccuracyReduced | ~5公里 | 最低 |
// 在 iOS 17 中已弃用,请使用 CLMonitor 替代
let region = CLCircularRegion(
center: coordinate,
radius: 100,
identifier: "MyRegion"
)
region.notifyOnEntry = true
region.notifyOnExit = true
manager.startMonitoring(for: region)
用于粗略跟踪的低功耗替代方案:
manager.startMonitoringSignificantLocationChanges()
// 约 500 米移动时更新,在后台工作
检测到达/离开:
manager.startMonitoringVisits()
func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) {
let arrival = visit.arrivalDate
let departure = visit.departureDate
let coordinate = visit.coordinate
}
// 动态区域管理
func updateMonitoredRegions(userLocation: CLLocation) async {
let nearbyPOIs = fetchNearbyPOIs(around: userLocation, limit: 20)
// 移除旧区域
for id in await monitor.identifiers {
if !nearbyPOIs.contains(where: { $0.id == id }) {
await monitor.remove(id)
}
}
// 添加新区域
for poi in nearbyPOIs {
let condition = CLMonitor.CircularGeographicCondition(
center: poi.coordinate,
radius: 100
)
await monitor.add(condition, identifier: poi.id)
}
}
<?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>
设置 → 隐私与安全 → 定位服务:
# 过滤位置日志
log stream --predicate 'subsystem == "com.apple.locationd"'
let locationTask = Task {
for try await update in CLLocationUpdate.liveUpdates() {
if Task.isCancelled { break }
processUpdate(update)
}
}
// 稍后
locationTask.cancel()
@MainActor
class LocationViewModel: ObservableObject {
@Published var currentLocation: CLLocation?
func startTracking() {
Task {
for try await update in CLLocationUpdate.liveUpdates() {
// 已经在 MainActor 上,可以安全地更新 @Published
self.currentLocation = update.location
}
}
}
}
Task {
do {
for try await update in CLLocationUpdate.liveUpdates() {
if update.authorizationDenied {
throw LocationError.authorizationDenied
}
processUpdate(update)
}
} catch {
handleError(error)
}
}
let geocoder = CLGeocoder()
func geocodeAddress(_ address: String) async throws -> CLLocation? {
let placemarks = try await geocoder.geocodeAddressString(address)
return placemarks.first?.location
}
// 使用区域设置获取本地化结果
let placemarks = try await geocoder.geocodeAddressString(
"1 Apple Park Way",
in: nil, // CLRegion 提示(可选)
preferredLocale: Locale(identifier: "en_US")
)
func reverseGeocode(_ location: CLLocation) async throws -> CLPlacemark? {
let placemarks = try await geocoder.reverseGeocodeLocation(location)
return placemarks.first
}
// 用法
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"
}
| 属性 | 示例 | 备注 |
|---|---|---|
name | "Apple Park" | 位置名称 |
thoroughfare | "Apple Park Way" | 街道名称 |
subThoroughfare | "1" | 街道号码 |
locality | "Cupertino" | 城市 |
subLocality | "Silicon Valley" | 街区 |
administrativeArea | "CA" | 州/省 |
postalCode | "95014" | 邮政编码 |
country | "United States" | 国家名称 |
isoCountryCode | "US" | ISO 国家代码 |
timeZone | America/Los_Angeles | 时区 |
location | CLLocation | 坐标 |
一次一个请求 — 如果请求正在进行,CLGeocoder 会抛出错误
Apple 速率限制 — 节流以避免 kCLErrorGeocodeCanceled
缓存结果 — 不要重新对相同地址/坐标进行地理编码
谨慎批量处理 — 在连续的地理编码请求之间添加延迟
// 检查地理编码器是否繁忙 if geocoder.isGeocoding { geocoder.cancelGeocode() // 在开始新的之前取消之前的 }
| 症状 | 检查项 |
|---|---|
| 没有位置更新 | 授权状态,Info.plist 键 |
| 后台不工作 | 后台模式能力,CLBackgroundActivitySession |
| 始终授权无效 | 带有 .always 的 CLServiceSession,在前台启动 |
| 地理围栏未触发 | 区域数量(最多 20 个),半径(最小约 100 米) |
| 只有降低的精度 | 检查 accuracyAuthorization,请求临时完全精度 |
| 定位图标保持开启 | 确保调用 stopUpdatingLocation() 或从异步循环中跳出 |
WWDC:2023-10180,2023-10147,2024-10212
文档:/corelocation,/corelocation/clmonitor,/corelocation/cllocationupdate,/corelocation/clservicesession
技能:axiom-core-location,axiom-core-location-diag,axiom-energy-ref
每周安装次数
93
仓库
GitHub 星标数
606
首次出现
2026 年 1 月 21 日
安全审计
安装于
opencode79
claude-code74
codex73
gemini-cli72
cursor70
github-copilot67
Comprehensive API reference for modern Core Location (iOS 17+).
axiom-core-location — Anti-patterns, decision trees, pressure scenariosaxiom-core-location-diag — Symptom-based troubleshootingaxiom-energy-ref — Location as battery subsystem (accuracy vs power)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:
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
}
}
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.
| Property | Type | Description |
|---|---|---|
location | CLLocation? | Current location (nil if unavailable) |
isStationary | Bool | True when device stopped moving |
authorizationDenied | Bool | User denied location access |
authorizationDeniedGlobally |
When device becomes stationary:
isStationary = true and valid locationisStationary = falseNo action required—happens automatically.
// 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)
Swift actor for monitoring geographic conditions and beacons.
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
}
}
CLMonitor.CircularGeographicCondition(
center: CLLocationCoordinate2D,
radius: CLLocationDistance // meters, minimum ~100m effective
)
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)
Maximum 20 conditions per app. Prioritize what to monitor. Swap regions dynamically based on user location if needed.
// If you know initial state
await monitor.add(condition, identifier: "Work", assuming: .unsatisfied)
Core Location will correct if assumption wrong.
// 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
| 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 |
lastEvent after handlingdidFinishLaunchingWithOptionsDeclarative authorization—tell Core Location what you need, not what to do.
// Hold session for duration of feature
let session = CLServiceSession(authorization: .whenInUse)
for try await update in CLLocationUpdate.liveUpdates() {
// Process updates
}
CLServiceSession(authorization: .none) // No auth request
CLServiceSession(authorization: .whenInUse) // Request When In Use
CLServiceSession(authorization: .always) // Request Always (must start in foreground)
// For features requiring precise location (e.g., navigation)
CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "NavigationPurpose" // Key in Info.plist
)
Requires NSLocationTemporaryUsageDescriptionDictionary in Info.plist.
Iterating CLLocationUpdate.liveUpdates() or CLMonitor.events creates implicit session with .whenInUse goal.
To disable implicit sessions:
<!-- Info.plist -->
<key>NSLocationRequireExplicitServiceSession</key>
<true/>
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
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
}
}
Sessions persist through:
On relaunch, recreate sessions immediately in didFinishLaunchingWithOptions.
| Status | Description |
|---|---|
.notDetermined | User hasn't decided |
.restricted | Parental controls prevent access |
.denied | User explicitly refused |
.authorizedWhenInUse | Access while app active |
.authorizedAlways | Background access |
| Value | Description |
|---|---|
.fullAccuracy | Precise location |
.reducedAccuracy | Approximate (~5km), updates every 15-20 min |
<!-- 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/>
@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
}
}
}
UIBackgroundModes with location value// 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
}
Blue status bar/pill appears when:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Recreate background session if was tracking
if wasTrackingLocation {
backgroundSession = CLBackgroundActivitySession()
startLocationUpdates()
}
return true
}
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
}
}
| Constant | Accuracy | Battery Impact |
|---|---|---|
kCLLocationAccuracyBestForNavigation | ~5m | Highest |
kCLLocationAccuracyBest | ~10m | Very High |
kCLLocationAccuracyNearestTenMeters | ~10m | High |
kCLLocationAccuracyHundredMeters | ~100m | Medium |
kCLLocationAccuracyKilometer |
// 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)
Low-power alternative for coarse tracking:
manager.startMonitoringSignificantLocationChanges()
// Updates ~500m movements, works in background
Detect arrivals/departures:
manager.startMonitoringVisits()
func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) {
let arrival = visit.arrivalDate
let departure = visit.departureDate
let coordinate = visit.coordinate
}
// 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)
}
}
<?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>
Settings → Privacy & Security → Location Services:
# Filter location logs
log stream --predicate 'subsystem == "com.apple.locationd"'
let locationTask = Task {
for try await update in CLLocationUpdate.liveUpdates() {
if Task.isCancelled { break }
processUpdate(update)
}
}
// Later
locationTask.cancel()
@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
}
}
}
}
Task {
do {
for try await update in CLLocationUpdate.liveUpdates() {
if update.authorizationDenied {
throw LocationError.authorizationDenied
}
processUpdate(update)
}
} catch {
handleError(error)
}
}
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")
)
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"
}
| 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 |
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 }
| 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 |
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
Weekly Installs
93
Repository
GitHub Stars
606
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykPass
Installed on
opencode79
claude-code74
codex73
gemini-cli72
cursor70
github-copilot67
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) |
| Too many conditions (max 20) |
conditionUnsupported | Condition type not available |
accuracyLimited | Reduced accuracy prevents monitoring |
| ~1km |
| Low |
kCLLocationAccuracyThreeKilometers | ~3km | Very Low |
kCLLocationAccuracyReduced | ~5km | Lowest |
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 |