Quickstart
Four steps from an empty project to a dashboard that says Connected. Requires iOS 18+, iPadOS 18+ or macOS 15+, and a Swift 6.2 toolchain.
1. Add the package
.package(url: "https://github.com/awizemann/swift-stats.git", from: "0.2.0")
.target(name: "MyApp", dependencies: [
.product(name: "Stats", package: "swift-stats"),
.product(name: "StatsCloudflare", package: "swift-stats")
])2. Configure once at launch
Constructing a client does no disk I/O, so this is safe on the main actor during launch. CloudflareEndpoint validates the URL and is HTTPS-only, so it throws.
import Stats
import StatsCloudflare
func makeStats(writeKey: String) throws -> StatsClient {
StatsClient(configuration: StatsConfiguration(
appId: "com.example.MyApp",
projectId: "myapp",
installIdSalt: "a-constant-per-app-string",
sink: CloudflareSink(
endpoint: try CloudflareEndpoint(string: "https://api.swiftstats.co"),
writeKey: writeKey
),
flushAt: 20,
flushInterval: .seconds(30),
autoEvents: [.appOpen, .appBackground, .sessions]
))
}3. Drive the lifecycle
The SDK installs no AppKit or UIKit observers — a library that silently hooks your app lifecycle isn't auditable. Two calls from scenePhase give you app open, app background and the flush on background.
.onChange(of: scenePhase) { _, phase in
Task {
switch phase {
case .active: await stats.applicationDidBecomeActive()
case .background: await stats.applicationDidEnterBackground()
default: break
}
}
}4. Track
Names are snake_case. Properties are flat and must never carry user text. track() returns once the event is on disk, not once it is sent.
await stats.track("project_opened", props: ["section": "analytics", "cached": true])Pick an
installIdSalt and never change it. It is not a secret and grants nothing; its only job is to stop the same random UUID being correlatable across apps. Changing it re-identifies every install as new.