Respectlytics Respect lytics
Menu
Swift (iOS) Level complete Privacy-first

How to track level-complete events in Swift (iOS) games without personal data

Level complete is the central progression event in mobile games and the place game analytics SDKs default to building per-player skill graphs (level number, time-to-complete, attempts, score, items used). Respectlytics helps developers avoid collecting personal data in the first place: in Swift (iOS), level complete is one named event per level, with the level encoded into the event name. Below: how to keep your level taxonomy bounded, why time-to-complete becomes a server-side derivation, and what stays in your game backend.

Fire on level-completion success — after the win-state animation begins, before any post-level dialog. Encode the level into the event name (level_complete_1, level_complete_2). Don't pass score, attempts, or duration.

Install the Swift (iOS) SDK

swift Respectlytics
// Package.swift
dependencies: [
    .package(url: "https://github.com/respectlytics/respectlytics-swift.git", from: "3.0.0")
]
// Or via Xcode → File → Add Packages → paste the URL above.

The SDK ships only via Swift Package Manager. CocoaPods and Carthage are not published — fewer integration paths means fewer surfaces to keep audited.

Initialize Respectlytics in Swift (iOS)

swift Respectlytics
import Respectlytics

@main
struct MyApp: App {
    init() {
        Respectlytics.configure(appKey: "<YOUR_APP_KEY>")
    }
    var body: some Scene { WindowGroup { ContentView() } }
}

Call configure once at app launch — typically in your App struct's init. No Info.plist keys are required: the SDK does not call ATTrackingManager and does not request the IDFA, so NSUserTrackingUsageDescription should NOT be added.

Track the event in Swift (iOS)

swift Respectlytics
import Respectlytics

func handleLevelComplete(levelNumber: Int) {
    // Encode level in event name; bucket by 50 if you have >50 levels.
    if levelNumber <= 50 {
        Respectlytics.track("level_complete_\(levelNumber)")
    } else {
        let bucket = ((levelNumber - 1) / 50) * 50 + 1
        let upper = bucket + 49
        Respectlytics.track("level_complete_\(bucket)_\(upper)")
    }
}

For high-level-count games, the bucketing strategy keeps your event-name taxonomy navigable — under 50 buckets total even for thousand-level games.

Privacy & implementation notes

Per-player progression — score curves, leaderboard positions, skill ratings, items inventory — lives in your game backend. That's where it has authority and where the gameplay logic reads from. Mirroring it into product analytics duplicates a system of record without adding signal you can't get from the game backend's own reporting.

Most game-product decisions are about rate ("is this level a difficulty wall for new players?") rather than skill ("how quickly does this player progress?"). The rate question is session-grouped. The skill question is per-player and lives in your game backend.

Apple rejected approximately 3% of apps in 2024 for incorrectly omitting NSUserTrackingUsageDescription when ATT was required by the SDKs they shipped. Respectlytics doesn't trigger ATT. The corollary is also true: do not add the key on Respectlytics's account — its presence implies you track across apps, even if your code never calls requestTrackingAuthorization.

Internally the Swift SDK uses Swift Concurrency: events are queued in an actor-isolated buffer (RAM-only), flushed on a 30-second timer and on UIApplication.willResignActiveNotification. Force-quit before flush drops queued events — by design. There is no UserDefaults or file backing.

How this compares to other analytics SDKs

Level complete eventFirebase AnalyticsMixpanelGameAnalyticsRespectlytics
Level number as parameterYesYesYesUse distinct event_name
Score storedRecommendedRecommendedYesOut of scope (game backend)
Attempts to completeRecommendedRecommendedYesOut of scope (game backend)
Time-to-completeRecommendedRecommendedYesServer-side derivation
Per-player progression curveYesYesYesOut of scope (game backend)

Frequently asked questions

Won't we run out of distinct event names if we have 1000 levels?

For high-level-count games, bucket: level_complete_1_50, level_complete_51_100, etc. The Respectlytics aggregation handles either shape. Per-level granular progression is a game-backend signal — your leaderboards and skill-rating system already have it.

How do we measure difficulty without per-player attempts?

Per-session. The rate of sessions where level_complete_N follows level_complete_N-1 is your level-N completion rate. Country-bucketed and device-platform-bucketed shows where the difficulty cliff sits.

What about retries?

Distinct event name: level_failed_N. The rate of level_failed_N to level_complete_N over a session is your level-N retry signal.

Should we instrument every level individually?

If you have under 50 levels, yes — distinct event names per level. Past that, bucket. The principle is to keep your event-name taxonomy navigable; 1000 distinct level event names is functionally a parameter.

Related guides

Track what matters. Collect nothing you don't.

Five-field event schema, RAM-only event queue, no IDFA, no AAID, no persistent user IDs. Helps developers avoid collecting personal data in the first place.