Phase
Get Started

Swift

llms.txt

Installation

Add Swift Package

Add Phase Analytics to your project using Swift Package Manager:

  1. In Xcode, go to File → Add Package Dependencies
  2. Enter the repository URL:

    https://github.com/Phase-Analytics/Phase-Swift

  3. Select the latest version
  4. Add to your target

Or add to Package.swift:

Swift
Package.swift
dependencies: [
    .package(url: "https://github.com/Phase-Analytics/Phase-Swift", from: "0.2.0")
]

Requirements:

  • iOS 15.0+ or macOS 12.0+
  • Swift 6.0+

Get Your API Key

  1. Sign in to Phase Dashboard
  2. Create a new project or select an existing one
  3. Open API Keys tab
  4. Copy your API Key (starts with phase_)

Setup

SwiftUI

Wrap your app with the Phase view to initialize the SDK:

Swift
MyApp.swift
import SwiftUI
import PhaseAnalytics

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            Phase(apiKey: "phase_xxx") {
                ContentView()
            }
        }
    }
}

The SDK initialization is asynchronous. Call identify() to register the device and start a session. Events tracked before identify() runs (even before initialization finishes) are buffered and sent afterwards.

Configuration

The Phase view and initialize() method accept the following parameters:

Prop

Type

Usage

Identify User

Call identify() once at startup. This registers the device and starts a session. Calling it before initialization finishes is fine: it runs right after initialization.

Swift
ContentView.swift
import SwiftUI
import PhaseAnalytics

struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
            .onAppear {
                Task {
                    // Initialize analytics - no PII collected by default
                    await PhaseSDK.shared.identify()
                }
            }
    }
}

Privacy by default:

  • No personal data is collected without explicit properties
  • Device ID is auto-generated and stored locally
  • Only technical metadata is collected (OS version, platform, locale, app version)

Adding custom properties:

You can optionally attach user properties. Important: If you add PII (personally identifiable information), ensure you have proper user consent:

// After user login and consent
await PhaseSDK.shared.identify([
    "user_id": "123",
    "plan": "premium",
    "beta_tester": true
])

// ⚠️ If adding PII, get consent first
let hasConsent = await customGetUserConsent()
if hasConsent {
    await PhaseSDK.shared.identify([
        "email": "[email protected]",
        "name": "John Doe"
    ])
}

Properties must be flat primitives: String, Int, Double, Bool, or nil. Keys can be up to 128 characters. Invalid properties are logged and left out; the device is still identified.

Track Events

Track custom events with optional parameters. Events tracked before identify() runs are buffered (up to 200 events, for up to one hour) with their original timestamps and sent once the session exists. Each event gets a unique ID when it is tracked, so retries never create duplicates.

// Event without parameters
track("app_opened")

// Event with parameters
track("purchase_completed", [
    "amount": 99.99,
    "currency": "USD",
    "product_id": "premium_plan"
])

// Using instance method
PhaseSDK.shared.track("button_clicked", params: ["button_id": "submit"])

Event naming rules:

  • Alphanumeric characters, underscores (_), hyphens (-), periods (.), forward slashes (/), and spaces
  • 1-256 characters
  • Examples: purchase, user.signup, payment/success, Button Clicked

Event parameters:

  • Flat primitive dictionary only
  • Values must be String, Int, Double, Bool, or nil
  • Max 32 keys
  • Key max 32 characters
  • String value max 256 characters
  • Serialized payload max 8 KB
  • Empty dictionaries are normalized away
  • NaN and infinite numbers are rejected

Screen Tracking

Use the .phaseScreen() modifier to automatically track screen views:

Swift
ProfileView.swift
import SwiftUI
import PhaseAnalytics

struct ProfileView: View {
    let userID: String

    var body: some View {
        VStack {
            Text("Profile")
        }
        .phaseScreen("ProfileView", params: ["user_id": userID])
    }
}

How it works:

Tracks a screen view each time the view appears, including when navigating back to it. Screen names are normalized automatically (e.g., "ProfileView" → "/profile-view") with CamelCase converted to kebab-case; query strings are removed and unsupported characters become -. Supports optional parameters.

Flush

Data is sent automatically in batches (see Delivery). flush() sends queued data now, for example before a test assertion or a known shutdown, and returns when the attempt finishes. While offline or while the server asks the SDK to back off, data stays queued and is retried automatically.

await PhaseSDK.shared.flush()

Reset

reset() forgets the current device, for example on logout. It ends the session, tries to deliver queued data, discards what could not be sent and generates a new device ID. Call identify() again to start tracking the new device.

await PhaseSDK.shared.reset()
await PhaseSDK.shared.identify()

Type Reference

DeviceProperties

Custom user/device attributes passed to identify():

Prop

Type

EventParams

Event parameters passed to track() and trackScreen():

Prop

Type

How It Works

Delivery

Everything goes through a persistent queue and is sent in batches of up to 50 items:

  • When 20 items are waiting, every 10 seconds while the queue is not empty, when the app moves to the background (inside a background task) and when connectivity returns
  • Items leave the queue only after the server answers. Items the server rejects permanently are dropped; items that failed for a transient reason are retried up to 5 times
  • Failed requests back off exponentially with jitter (up to 5 minutes) and honor Retry-After on 429/503. Requests time out after 15 seconds
  • Session heartbeats are coalesced to one pending heartbeat per session

Offline Support

The queue is stored in UserDefaults (up to 1000 items) and survives app restarts. Queued data is sent when connection is restored.

Privacy

  • No personal data is collected by default
  • Device IDs are generated locally and stored persistently
  • Geolocation is resolved server-side from IP address (disable with properties)
  • All data collection is optional via configuration

Performance

  • Queued data is batched and sent asynchronously
  • Network state is monitored automatically
  • Failed requests retry with exponential backoff
  • Maximum batch size: 50 items per request
  • Queue writes to storage happen at most once per second (and immediately on backgrounding)
  • Thread-safe with Swift 6 concurrency