Skip to content

Geofence

public structGeofence

A geofence definition for monitoring circular or polygon regions.

The SDK implements the native iOS and Android Geofencing APIs, extended with polygon support and a proximity-based infinite-geofencing system that overcomes the platform limits of 20 (iOS) and 100 (Android) simultaneous geofences.

Contents

Overview
Field Required Description
identifier Unique name for this geofence.
latitude ✅* Center latitude (*omit for polygon geofences).
longitude ✅* Center longitude (*omit for polygon geofences).
radius ✅* Radius in meters (*omit for polygon geofences).
notifyOnEntry Fire event on entry.
notifyOnExit Fire event on exit.
notifyOnDwell Fire event after loitering for loiteringDelay ms.
vertices Polygon geofence vertices (replaces lat/lng/radius).
extras Arbitrary key-value metadata posted with each event.

Warning

Both platforms require GeolocationConfig.locationAuthorizationRequest to be "Always". Geofencing does not work with "WhenInUse" only.


Adding geofences

Use BGGeo.addGeofence for a single geofence, or BGGeo.addGeofences for bulk inserts (approximately 10× faster than inserting individually).

If a geofence with the same identifier already exists in the database, it is replaced.


Listening for events

Subscribe to geofence transitions with BGGeo.onGeofence. Subscribe to changes in the actively monitored set with BGGeo.onGeofencesChange.

let bgGeo = BGGeo.shared

// Listen for geofence events.
let sub = bgGeo.onGeofence { geofence in
    print("[geofence] \(geofence.identifier) \(geofence.action)")
}

Note

When all geofences are removed, BGGeo.onGeofencesChange fires with empty arrays for both on and off.


Polygon geofencing

The SDK supports polygon geofences of any shape via the vertices field. Polygon geofencing is sold as a separate add-on but is fully functional in DEBUG builds.

When defining a polygon geofence, omit latitude, longitude, and radius — the SDK calculates the minimum enclosing circle automatically from the polygon geometry.

let bgGeo = BGGeo.shared

let sub = bgGeo.onGeofencesChange { event in
    let on = event.on   // <-- new geofences activated.
    let off = event.off // <-- geofences that were just de-activated.

    // Create map circles
    on.forEach { geofence in
        print("[geofencesChange] activated: \(geofence.identifier)")
    }

    // Remove map circles
    off.forEach { identifier in
        print("[geofencesChange] deactivated: \(identifier)")
    }
}

Infinite geofencing

The SDK stores all geofences in its database and uses a geospatial query to activate only the nearest geofences within GeolocationConfig.geofenceProximityRadius, staying within the platform limit. As the device moves, the active set is periodically refreshed.

  • The minimum GeolocationConfig.geofenceProximityRadius is enforced at 1000 meters.
  • Geofences within the radius (green in the diagram below) are actively monitored.
  • Geofences outside the radius (grey) remain in the database but are dormant.


Removing geofences

Geofences persist in the SDK's database until explicitly removed. If AppConfig.stopOnTerminate is false and AppConfig.startOnBoot is true, geofences continue to be monitored across app termination and device reboots.

let bgGeo = BGGeo.shared

Task {
    do {
        try await bgGeo.geofences.remove("HOME")
        print("[removeGeofence] success")
    } catch {
        print("[removeGeofence] FAILURE: \(error)")
    }
}
let bgGeo = BGGeo.shared

Task {
    do {
        try await bgGeo.geofences.removeAll()
        print("[removeGeofences] all geofences have been destroyed")
    } catch {
        print("[removeGeofences] FAILURE: \(error)")
    }
}
let bgGeo = BGGeo.shared

let geofences = bgGeo.geofences.getAll()
print("[getGeofences] \(geofences)")

Geofences-only mode

Call BGGeo.startGeofences instead of BGGeo.start to monitor geofences without continuous location tracking. Use GeolocationConfig.geofenceModeHighAccuracy to improve event responsiveness at the cost of higher power usage.

The SDK can switch between full tracking and geofences-only mode at any time by calling the corresponding start method.

let bgGeo = BGGeo.shared

let sub = bgGeo.onGeofence { geofence in
    print("[geofence] \(geofence)")
}

bgGeo.ready { config in
    config.http.url = "http://your.server.com/geofences"
    config.http.autoSync = true
}

Task {
    do {
        // engage geofences-only mode:
        try await bgGeo.startGeofences()
    } catch {
        print("[startGeofences] error: \(error)")
    }
}

Examples
let bgGeo = BGGeo.shared

// Listen to geofence events
let sub = bgGeo.onGeofence { geofence in
    print("[geofence] \(geofence)")
    if geofence.identifier == "DANGER_ZONE" {
        if geofence.action == "ENTER" {
            // Entering the danger-zone, we want to aggressively track location.
            Task { try await bgGeo.start() }
        } else if geofence.action == "EXIT" {
            // Exiting the danger-zone, we resume geofences-only tracking.
            Task { try await bgGeo.startGeofences() }
        }
    }
}

// Add a geofence.
let geofence = Geofence(
    identifier: "DANGER_ZONE",
    radius: 1000,
    latitude: 45.51921926,
    longitude: -73.61678581,
    notifyOnEntry: true,
    notifyOnExit: true
)

Task {
    try await bgGeo.geofences.add(geofence)
}

// Ready the plugin.
bgGeo.ready { config in
    config.geolocation.desiredAccuracy = kCLLocationAccuracyBest
    config.geolocation.distanceFilter = 10
    config.http.url = "http://your.server.com/locations"
    config.http.autoSync = true
}

Task {
    try await bgGeo.startGeofences()
}
Single geofence
let bgGeo = BGGeo.shared

let geofence = Geofence(
    identifier: "Home",
    radius: 200,
    latitude: 45.51921926,
    longitude: -73.61678581,
    notifyOnEntry: true,
    notifyOnExit: true,
    extras: ["route_id": 1234]
)

Task {
    do {
        try await bgGeo.geofences.add(geofence)
        print("[addGeofence] success")
    } catch {
        print("[addGeofence] FAILURE: \(error)")
    }
}
Multiple geofences
let bgGeo = BGGeo.shared

let geofences = [
    Geofence(
        identifier: "Home",
        radius: 200,
        latitude: 45.51921926,
        longitude: -73.61678581,
        notifyOnEntry: true
    ),
    Geofence(
        identifier: "Work",
        radius: 200,
        latitude: 45.61921927,
        longitude: -73.71678582,
        notifyOnEntry: true
    )
]

Task {
    do {
        try await bgGeo.geofences.addAll(geofences)
        print("[addGeofences] success")
    } catch {
        print("[addGeofences] FAILURE: \(error)")
    }
}

Members

entryState

public let entryState: EntryState

Current entry state of the geofence.

Value State
0 Outside
1 Inside

extras

public let extras: [String: Any]?

Arbitrary key-value metadata attached to each geofence event and included in the payload posted to HttpConfig.url.

hits

public let hits: Int

Number of times this geofence has been triggered since it was added.

identifier

public let identifier: String

Unique identifier for this geofence.

Used to reference the geofence in events and removal calls. Adding a geofence with an identifier that already exists replaces the existing one.

latitude

public let latitude: Double

Latitude of the circular geofence center.

Omit when defining a polygon geofence via vertices — the SDK calculates the center automatically.

loiteringDelay

public let loiteringDelay: Double

Minimum time in milliseconds the device must remain inside the geofence before a notifyOnDwell event fires. Default 0.

longitude

public let longitude: Double

Longitude of the circular geofence center.

Omit when defining a polygon geofence via vertices — the SDK calculates the center automatically.

notifyOnDwell

public let notifyOnDwell: Bool

Fire a GeofenceEvent when the device has remained inside this geofence for loiteringDelay milliseconds.

notifyOnEntry

public let notifyOnEntry: Bool

Fire a GeofenceEvent when the device enters this geofence.

See also - GeolocationConfig.geofenceInitialTriggerEntry

notifyOnExit

public let notifyOnExit: Bool

Fire a GeofenceEvent when the device exits this geofence.

radius

public let radius: Double

Radius of the circular geofence in meters.

Omit when defining a polygon geofence via vertices — the SDK calculates the enclosing radius automatically.

Warning

The minimum reliable radius is 200 meters. Below this threshold, geofences may not trigger reliably on either platform.

Apple documents this explicitly:

"For testing purposes, you can assume that the minimum distance is approximately 200 meters."

stateUpdatedAt

public let stateUpdatedAt: Date?

Epoch timestamp in seconds of the last geofence transition.

vertices

public let vertices: [[Double]]?

Polygon geofence vertices as [[lat, lng], ...] pairs.

When provided, the geofence is treated as a polygon rather than a circle. Omit latitude, longitude, and radius — the SDK solves the minimum enclosing circle from the vertex geometry and registers that as the native circular geofence.

When the device enters the enclosing circle, the SDK begins C++ hit-testing against the polygon at high frequency. When it exits the circle, polygon monitoring ceases.

Note

Polygon geofencing is sold as a separate add-on but is fully functional in DEBUG builds.

let bgGeo = BGGeo.shared

let geofence = Geofence(
    identifier: "Home",
    vertices: [
        [45.518947279987714, -73.6049889209514],   // <-- [lat, lng]
        [45.5182711292279, -73.60338649600598],
        [45.517082240237634, -73.60432670908212],
        [45.51774871402813, -73.60604928622278]
    ],
    notifyOnEntry: true,
    notifyOnExit: true
)

Task {
    do {
        try await bgGeo.geofences.add(geofence)
    } catch {
        print("[addGeofence] FAILURE: \(error)")
    }
}