Methods
Everything hangs off the BackgroundGeolocation enum. Methods that talk to the
engine are async, and the whole type is @MainActor-isolated: every engine
timer and CoreLocation callback needs a live run loop, so the facade keeps all
of it on the main actor rather than making you reason about which calls are
safe from where.
All methods at a glance
| Method | Returns | Notes |
|---|---|---|
ready(_:) | State | Configure; does not start tracking |
setConfig(_:) | State | Change config on a running engine |
start() | State | Begin tracking; persists |
stop() | State | Stop tracking |
getState() | State | Snapshot + health fields |
changePace(_:) | Void | Force the motion state |
getCurrentPosition(_:) | Location | One-shot fix |
watchPosition(_:) | Void | High-frequency stream |
stopWatchPosition() | Void | |
requestPermission() | AuthorizationStatus | No requester object needed |
requestTemporaryFullAccuracy(purpose:) | AccuracyAuthorization | iOS-only |
getProviderState() | ProviderState | Grant + provider availability |
isPowerSaveMode() | Bool | Low Power Mode |
getOdometer() | Double | Metres |
setOdometer(_:) | Location | |
resetOdometer() | Location | setOdometer(0) |
addGeofence(_:) | Void | |
addGeofences(_:) | Void | |
removeGeofence(identifier:) | Void | |
removeGeofences() | Void | All of them |
getGeofences() | [Geofence] | |
geofenceExists(identifier:) | Bool | |
sync() | [Location] | Flush the queue now |
getCount() | Int | Records pending |
getLocations() | [Location] | Read the queue |
destroyLocations() | Int | Empty the queue |
destroyLocation(uuid:) | Void | Drop one record |
insertLocation(_:) | Void | Inject a record |
getAuthState() | (accessToken:refreshToken:) | Current token pair |
getLog(limit:) | [LogEntry] | |
destroyLog() | Int | |
uploadLog() | Int | |
logger.info(_:) etc. | Void | Write your own lines |
onX(_:) | Subscription | See Events |
There is no attach() and no headless registration: the engine installs its
own launch observer, so a process iOS relaunches for a location event has the
engine up before your code runs.
Lifecycle
ready()
let state = try await BackgroundGeolocation.ready(Config(distanceFilter: 10))Applies configuration and brings the engine up. Does not start tracking —
state.enabled tells you whether tracking is already running from a previous
process, which is the normal case after a reboot.
Throws a BGeoError with a LICENSE_* code in a release build with a bad key. The config is
applied before the licence is checked, so a licence failure still leaves your
configuration in place.
setConfig()
try await BackgroundGeolocation.setConfig(Config(distanceFilter: 50))Changes configuration on a running engine — power keys (distanceFilter,
disableElasticity, locationUpdateInterval) take effect on the live location
request without a stop/start cycle.
Only the keys you set are changed; Config’s null defaults are omitted from
the patch rather than sent as nulls.
start()
try await BackgroundGeolocation.start()try await BackgroundGeolocation.stop()stop()
start() checks the licence first, then persists the intent — tracking
survives backgrounding, process death and reboot until stop(). stop() never
consults the licence: an expired key can never trap a user in a tracking state
they cannot turn off.
getState()
let state = await BackgroundGeolocation.getState()state.enabled // typedstate["lastAcceptedFixAge"] as? Double // health fields, from rawSee State for the fields the
Android engine reports.
changePace()
try await BackgroundGeolocation.changePace(true)Forces the motion state machine into moving or stationary — the manual
override for “I know a trip just started”. Throws BGeoError with code DISABLED
when tracking is off, rather than silently doing nothing.
Geolocation
getCurrentPosition()
let location = try await BackgroundGeolocation.getCurrentPosition( CurrentPositionOptions(persist: false, samples: 3, timeout: 30))One-shot fix, independent of start(): it works while tracking is off and does
not turn tracking on. samples collects several fixes and returns the most
accurate; persist = false keeps it out of the upload queue.
Throws on timeout or permission failure.
watchPosition()
BackgroundGeolocation.watchPosition(WatchPositionOptions(interval: 1))stopWatchPosition()
BackgroundGeolocation.stopWatchPosition()A high-frequency stream for a foreground map. There is no separate channel:
fixes arrive through the ordinary
locations stream.
Failures arrive as locationerror
events, not exceptions — including “called on an unlicensed build”. Without a
subscriber there, a failing watch fails silently. It also drains battery fast;
stop it when the screen you opened it for goes away.
Permissions & providers
requestPermission()
let status = try await BackgroundGeolocation.requestPermission()Prompts for authorization and returns the final
AuthorizationStatus.
No requester object: iOS presents its own dialog. Whether it offers Always
depends on locationAuthorizationRequest
and on how many times it has been asked — see
Permissions, the upgrade prompt is a
one-shot.
requestTemporaryFullAccuracy()
let accuracy = await BackgroundGeolocation.requestTemporaryFullAccuracy( purpose: "DeliverFullAccuracy")Asks a user who granted approximate location for precise location for the
rest of this app run. purpose must be a key in your
NSLocationTemporaryUsageDescriptionDictionary; if it is not, iOS may never
invoke the completion, so this call is bounded by a 30-second watchdog that
resolves with the unchanged authorization rather than hanging forever.
getProviderState()
let provider = await BackgroundGeolocation.getProviderState()if !provider.enabled { /* location services are off device-wide */ }isPowerSaveMode()
if await BackgroundGeolocation.isPowerSaveMode() { /* expect a sparser track */ }Odometer
getOdometer()
setOdometer()
resetOdometer()
let metres = await BackgroundGeolocation.getOdometer()_ = try await BackgroundGeolocation.setOdometer(0)_ = try await BackgroundGeolocation.resetOdometer() // the same thing, namedThe odometer accumulates across sessions and process deaths; it only moves for
fixes that pass the filter (and, if odometerAccuracyThreshold is set, an
extra accuracy gate). setOdometer returns the fix it used to re-anchor.
Geofences
addGeofence()
addGeofences()
removeGeofence()
removeGeofences()
getGeofences()
geofenceExists()
try await BackgroundGeolocation.addGeofence( Geofence( identifier: "home", radius: 200, latitude: 52.52, longitude: 13.405, notifyOnEntry: true, notifyOnExit: true ))try await BackgroundGeolocation.addGeofences([/* … */])await BackgroundGeolocation.removeGeofence(identifier: "home")await BackgroundGeolocation.removeGeofences()let fences = await BackgroundGeolocation.getGeofences()let exists = await BackgroundGeolocation.geofenceExists(identifier: "home")Geofences persist across process death in the engine’s own database — add them
once, not on every launch. Adding an identifier that already exists replaces
it. An invalid definition (no identifier, non-positive radius, bad coordinates)
throws BGeoError with code INVALID_GEOFENCE.
iOS allows an app 20 monitored regions, and the engine spends one of them
on its own wake region — so 19 of yours are armed at a time. It keeps the
nearest ones registered and swaps as you move, reporting each swap through
geofenceschange. Every fence
you added still exists regardless of what is currently armed.
See Geofencing.
Upload queue
sync()
getCount()
getLocations()
destroyLocation()
destroyLocations()
insertLocation()
getAuthState()
let flushed = try await BackgroundGeolocation.sync() // upload nowlet pending = await BackgroundGeolocation.getCount() // how many are waitinglet records = await BackgroundGeolocation.getLocations() // read without uploadingtry await BackgroundGeolocation.destroyLocation(uuid: uuid) // drop onelet dropped = await BackgroundGeolocation.destroyLocations()await BackgroundGeolocation.insertLocation(record) // inject onelet tokens = await BackgroundGeolocation.getAuthState()sync() returns the records it flushed. It ignores
disableAutoSyncOnCellular — an explicit sync is an explicit instruction.
getAuthState() returns the token pair the engine currently holds, which
matters when the engine refreshed a JWT natively and your app keeps its own
copy. See HTTP & authorization.
Logger
getLog()
uploadLog()
destroyLog()
BackgroundGeolocation.logger.info("checkout started", data: ["cart": 3])
let entries = await BackgroundGeolocation.getLog(limit: 200)let uploaded = await BackgroundGeolocation.uploadLog()let deleted = await BackgroundGeolocation.destroyLog()Your lines land in the same on-device log as the engine’s diagnostics and ride
the same upload path to logUrl, so an app event and the engine’s reaction to
it sit next to each other on one timeline. logger.error/warn/info/debug/verbose
mirror the levels in
LogLevel; anything above the
configured level is dropped rather than stored.
See Logging & debugging.
Events
onLocation, onMotionChange, onProviderChange, onHeartbeat, onHttp,
onConnectivityChange, onPowerSaveChange, onAuthorization,
onLocationError, onGeofence, onGeofencesChange, and the matching Flow
properties — all in Events.