Skip to content

iOS setup

BeekonKit is a Swift package distributed as a signed binary .xcframework. iOS 17 is the minimum — the SDK is built on CLLocationUpdate.liveUpdates plus CLBackgroundActivitySession, with CLServiceSession(authorization: .always) on iOS 18+. Your app interacts with a single Beekon.shared actor.

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(.v17)],
dependencies: [
.package(url: "https://github.com/wayqteam/beekon-ios-binary.git", from: "0.0.3"),
],
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 {
await Beekon.shared.configure(
BeekonConfig(intervalSeconds: 30, distanceMeters: 100)
)
}
}
var body: some Scene { WindowGroup { ContentView() } }
}

There’s no separate initialize()Beekon.shared is an actor and is auto-initialized. configure is async and non-throwing; it’s a setter and may be called again at any time, including while tracking, to live-tune the gate.

For cold-launch resume after process death, call await Beekon.shared.start() early in the App init. The SDK reads the last persisted BeekonConfig from a UserDefaults suite (in.wayq.beekon) and resumes; if no intent is saved, it falls back to BeekonConfig.default (30s / 100m).

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.storageFailure(let underlying) {
// SQLite/filesystem failure — log and surface
}

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

The fix stream is CLLocationUpdate.liveUpdates (the modern async API) wrapped in an AsyncStream for callers. CLBackgroundActivitySession is held to keep background delivery alive; on iOS 18+ a CLServiceSession(authorization: .always) is taken alongside it.

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