VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Groups
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL
Swift SDK
Swift quickstart
Display a paywall
Make and restore purchases
Check access
Identify users
Evaluate feature flags
Capture analytics
Groups
Track screens
Development mode
Swift SDK reference
Errors
Diagnostic codes
Troubleshooting

Track screens

Record the screens people visit, automatically in UIKit and with one modifier in SwiftUI.

The SDK records a built-in $screen event each time the user lands on a new screen. Each event carries the screen the user came from and how long they stayed there, so one event describes a whole transition. Screen tracking is on by default and needs analytics consent.

UIKit

Nothing to add. When a view controller from your app appears, the SDK records it. Framework containers such as navigation and tab bar controllers are skipped, and so are child controllers embedded inside a screen. The screen name is the class name, and the path is the chain of parent controllers, such as /MainTabBarController/HomeViewController.

A view controller belongs to your app when its class is defined in your app's main bundle. Two setups need more than that rule:

  • Feature modules in dynamic frameworks. Controllers defined in an embedded framework are not in the main bundle, so they are not recorded.
  • Statically linked libraries. A library linked into your app binary, which is the Swift Package Manager default, places its controllers in the main bundle. A payment sheet or an image picker from such a library is then recorded as one of your screens.

Set isAppScreenClass to decide which classes are your screens:

let featureBundle = Bundle(for: CheckoutViewController.self)

var options = VoidhashOptions()
options.screenTracking.isAppScreenClass = { type in
    Bundle(for: type) == .main || Bundle(for: type) == featureBundle
}

Container and child controllers are still skipped, and mapScreen still applies afterwards.

SwiftUI

SwiftUI navigation does not expose view controllers, so mark each screen with a modifier:

struct CheckoutView: View {
    var body: some View {
        content
            .voidhashScreen("Checkout")
    }
}

Pass title: to record a display title as well: .voidhashScreen("Checkout", title: "Your order"). The screen is recorded when the view appears. Once a SwiftUI screen has been recorded, the hosting controller is no longer reported by the automatic UIKit tracking. This includes your own UIHostingController subclasses, so a SwiftUI screen pushed from UIKit is recorded once per visit.

Custom navigation

Call screen() yourself when your app drives navigation another way, such as onboarding steps or pager pages. Each call records a new screen, even with the same name:

await voidhash.screen("Onboarding step 2", properties: ["step": .number(2)])

Event properties

PropertyMeaning
$screen_nameStable identity of the screen. Never contains ids.
$screen_pathConcrete location.
$screen_titleThe navigation item title when one is set.
$previous_screen_nameScreen the user came from. null on the first screen.
$previous_screen_pathPath of the previous screen.
$previous_screen_duration_msMilliseconds spent on the previous screen, including time in the background.
$screen_sourceuikit, swiftui or manual.

A screen is recorded once when it becomes visible. Returning from the background does not record it again. Navigating back to an earlier screen does.

Options

Configure tracking when you configure the SDK:

var options = VoidhashOptions()
options.screenTracking.automatic = false
options.screenTracking.mapScreen = { view in
    view.name.hasPrefix("Debug") ? nil : view
}
Voidhash.configure(publishableKey: "vh_pk_...", options: options)

Set automatic to false to stop the SDK from observing view controllers. mapScreen lets you rename a screen or drop it by returning nil. isAppScreenClass decides which view controller classes count as your screens, as described in UIKit.

To stop accepting screen events for a whole project without an app release, turn off $screen in the project's event settings. See Events.

Next steps

  • Capture analytics
  • Events
  • Analytics reports

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousGroupsNextDevelopment mode

On this page

UIKitSwiftUICustom navigationEvent propertiesOptionsNext steps