Skip to content

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

MethodReturnsNotes
ready(_:)StateConfigure; does not start tracking
setConfig(_:)StateChange config on a running engine
start()StateBegin tracking; persists
stop()StateStop tracking
getState()StateSnapshot + health fields
changePace(_:)VoidForce the motion state
getCurrentPosition(_:)LocationOne-shot fix
watchPosition(_:)VoidHigh-frequency stream
stopWatchPosition()Void
requestPermission()AuthorizationStatusNo requester object needed
requestTemporaryFullAccuracy(purpose:)AccuracyAuthorizationiOS-only
getProviderState()ProviderStateGrant + provider availability
isPowerSaveMode()BoolLow Power Mode
getOdometer()DoubleMetres
setOdometer(_:)Location
resetOdometer()LocationsetOdometer(0)
addGeofence(_:)Void
addGeofences(_:)Void
removeGeofence(identifier:)Void
removeGeofences()VoidAll of them
getGeofences()[Geofence]
geofenceExists(identifier:)Bool
sync()[Location]Flush the queue now
getCount()IntRecords pending
getLocations()[Location]Read the queue
destroyLocations()IntEmpty the queue
destroyLocation(uuid:)VoidDrop one record
insertLocation(_:)VoidInject a record
getAuthState()(accessToken:refreshToken:)Current token pair
getLog(limit:)[LogEntry]
destroyLog()Int
uploadLog()Int
logger.info(_:) etc.VoidWrite your own lines
onX(_:)SubscriptionSee 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 trackingstate.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 // typed
state["lastAcceptedFixAge"] as? Double // health fields, from raw

See 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, named

The 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 now
let pending = await BackgroundGeolocation.getCount() // how many are waiting
let records = await BackgroundGeolocation.getLocations() // read without uploading
try await BackgroundGeolocation.destroyLocation(uuid: uuid) // drop one
let dropped = await BackgroundGeolocation.destroyLocations()
await BackgroundGeolocation.insertLocation(record) // inject one
let 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.