Skip to content

iOS setup

BeekonKit is a Swift package distributed as a signed binary .xcframework. iOS 15 is the minimum; iOS 17+ uses the modern CLLocationUpdate.liveUpdates API and iOS 15–16 falls back to the CLLocationManager delegate. The fallback is internal — your app sees the same Beekon.shared actor on every OS version.

In Xcode: File → Add Package Dependencies… then paste:

https://github.com/wayqteam/beekon-ios-binary.git

Select the version (or branch main for latest) and add BeekonKit to your app target.

For SwiftPM-driven projects (Package.swift):

let package = Package(
// …
platforms: [.iOS(.v15)],
dependencies: [
.package(url: "https://github.com/wayqteam/beekon-ios-binary.git", from: "0.0.1"),
],
targets: [
.target(name: "App", dependencies: [
.product(name: "BeekonKit", package: "beekon-ios-binary"),
]),
]
)

Add usage descriptions and the location background mode. The strings appear in the system permission dialog — write them in your app’s voice.

<key>NSLocationWhenInUseUsageDescription</key>
<string>Used to show your live position in the app.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Used to keep tracking your trips when the app is in the background.</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>

fetch and processing are not required for v1 — only location.

Beekon does not request location permission for you. The host app drives the prompt because the right time and copy depend on your UX. You must reach Always authorization before background tracking will work.

  1. Request whenInUse first — required by iOS before you can ever ask for always.
  2. After the user has granted whenInUse and used the relevant feature once, request always. iOS will deny silently if you ask too aggressively.
  3. Call Beekon.shared.start() once authorization is always.

The sample app at beekon-ios/Sample/LocationPermissionManager.swift shows the canonical two-step prompt — the relevant lines are:

private let manager = CLLocationManager()
func requestWhenInUse() { manager.requestWhenInUseAuthorization() }
func requestAlways() { manager.requestAlwaysAuthorization() }

Wire these to two buttons in your onboarding rather than firing both at once.

import BeekonKit
@main
struct MyApp: App {
init() {
Task {
try await Beekon.shared.initialize()
await Beekon.shared.configure(BeekonConfig(preset: .balanced))
}
}
var body: some Scene { WindowGroup { ContentView() } }
}

initialize is async throws and idempotent. configure is async and non-throwing — it’s a setter.

Beekon is an actor — every method is safe to call from any context, and the streams are hot (every consumer sees the latest state and positions).

do {
try await Beekon.shared.start()
} catch BeekonError.permissionDenied {
// user denied or hasn't granted Always — drive the prompt
} catch BeekonError.locationServicesDisabled {
// Settings → Privacy → Location Services is OFF system-wide
} catch BeekonError.notConfigured {
// configure(…) was never called
} catch BeekonError.notInitialised {
// initialize() was never called
} catch BeekonError.internalError(let underlying) {
// log and surface — should be rare
}

See Errors for the full taxonomy and what to do about each.

iOS versionMechanism
iOS 17+CLLocationUpdate.liveUpdates async API. CLBackgroundActivitySession is held to keep background delivery alive. iOS 18+ additionally takes a CLServiceSession(authorization: .always).
iOS 15–16Legacy CLLocationManager delegate with allowsBackgroundLocationUpdates = true and showsBackgroundLocationIndicator = true. Wrapped as an AsyncStream so callers see one shape.

A Significant Location Change monitor runs alongside on every version — it’s the only mechanism that can wake your app from terminated state. Beekon persists tracking intent in a UserDefaults suite (com.wayq.beekon) so an SLC-driven relaunch into a fresh process auto-resumes tracking.