Respectlytics Respect lytics
Menu
Swift (iOS) Video completion Privacy-first

How to track video completions in Swift (iOS) without personal data

Video engagement events — play, 25% / 50% / 75% milestones, complete — are dense signals for content apps. Most analytics SDKs default to tagging them with the video ID, content category, and watch duration, building a per-user watch history along the way. Respectlytics helps developers avoid collecting personal data in the first place: in Swift (iOS), each milestone is its own named event, with no metadata. Below: how to wire to the player's progress callbacks, why milestones beat raw position, and what content metadata stays in your CMS.

Fire the call at meaningful playback milestones: video_play_started, video_25pct_reached, video_50pct_reached, video_75pct_reached, video_completed. Don't pass video ID, content title, 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
import AVKit

class PlayerObserver {
    private var fired25 = false, fired50 = false, fired75 = false, firedComplete = false

    func attach(to player: AVPlayer) {
        let interval = CMTime(seconds: 1, preferredTimescale: 1)
        player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
            guard let duration = player.currentItem?.duration.seconds, duration.isFinite else { return }
            let progress = time.seconds / duration
            if progress >= 0.25 && !self.fired25 { self.fired25 = true; Respectlytics.track("video_25pct_reached") }
            if progress >= 0.50 && !self.fired50 { self.fired50 = true; Respectlytics.track("video_50pct_reached") }
            if progress >= 0.75 && !self.fired75 { self.fired75 = true; Respectlytics.track("video_75pct_reached") }
            if progress >= 0.99 && !self.firedComplete { self.firedComplete = true; Respectlytics.track("video_completed") }
        }
    }
}

Reset the fired* flags whenever the user starts a new video, otherwise subsequent videos won't re-fire the milestones.

Privacy & implementation notes

Tracking exact playback position generates 30+ events per minute of viewing and dilutes every other signal in your analytics pipeline. Four milestones (25, 50, 75, 100%) per video carry essentially the same product signal at 1/100th the volume.

Your CMS already stores video titles, descriptions, durations, and content categories. Joining video_completed_movie events to your CMS's view-count-per-video data answers most product questions; storing the title as analytics metadata duplicates a system of record without adding signal.

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

Video completion eventFirebase AnalyticsMixpanelRespectlytics
Video ID as parameterRecommendedRecommendedForbidden (use top-N event_names)
Content category / genreRecommendedRecommendedUse distinct event_name (top-N)
Watch duration in secondsRecommendedRecommendedOut of scope (use milestones)
Per-user watch historyYesYesOut of scope (use content backend)
Completion *rate* by country / platformYesYesYes (default)

Frequently asked questions

How do we know which videos are most-watched?

Your content backend already has play counts per video — that's where video-level engagement lives. Respectlytics is for product engagement at the feature level ("is the video player surface working?"). Conflating them duplicates a system of record.

Should we track exact playback position?

No — milestones (25 / 50 / 75 / 100%) carry the signal. Exact position produces a flood of low-value events that drown the milestone signal. Player-side analytics (your video player vendor's SDK) has its own per-second tracking if you genuinely need it.

What about content category breakdowns (movies, shows, shorts)?

If you have a small fixed set, distinct event names: video_completed_movie, video_completed_show. For high-cardinality categorization, use your content backend's analytics — it has the metadata you need.

Does this work for live streams?

Conceptually different. Use distinct event names — livestream_joined, livestream_left — and fire on join and leave. Milestones don't apply to live; duration is computed from join → leave timestamp difference.

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.