Skip to main content
Configure automatic screen and interaction capture for iOS applications using the Userpilot iOS SDK. The Userpilot iOS SDK can automatically capture UIKit and SwiftUI screens and interactions, enabling analytics and engagement without manual screen and event tracking. Use this guide for SDK-side configuration, screen customization, privacy controls, and custom clickable views.

SDK Configuration

Auto capture is off by default. Auto capture behavior is configured through Userpilot.Config during SDK initialization.
Userpilot(config: Userpilot.Config(token: "<APP_TOKEN>")
    .logging(enabled: true)
    .appFramework(.uiKit) // or .swiftUI
    .enableScreenAutoCapture(true)
    .enableInteractionAutoCapture(true)
    .enableInteractionTextCapture(true)
    .enableInteractionAccessibilityLabelCapture(true)
    .enableInteractionValueCapture(true)
)
OptionTypeDefaultDescription
enableScreenAutoCaptureBoolfalseEnables automatic screen tracking when view controllers or SwiftUI views appear.
enableInteractionAutoCaptureBoolfalseEnables automatic interaction capture for taps, value changes, text input, and selections.
enableScreenTitleCaptureBooltrueIf false, navigation and tab titles are not captured in screen payloads.
enableInteractionTextCaptureBooltrueIf false, user-visible text is redacted from captured interaction payloads.
enableInteractionAccessibilityLabelCaptureBooltrueIf false, accessibility labels are not captured. Disable alongside enableInteractionTextCapture(false) when labels may mirror on-screen text.
enableInteractionValueCaptureBooltrueIf false, values from controls such as switches, sliders, and pickers are not captured.
ignoreTapForTextInputEditingActionsBooltrueIf true, prevents duplicate tap events for text input editing actions.
preferUIKitOverSwiftUIForNavigationBarBooltrueIf true, prefers UIKit events over SwiftUI for navigation bar interactions to avoid duplicates.
appFrameworkAppFrameworknilFramework hint: .uiKit or .swiftUI.
Example: disable all text and accessibility label capture for maximum privacy:
Userpilot(config: Userpilot.Config(token: "<APP_TOKEN>")
    .appFramework(.uiKit)
    .enableScreenAutoCapture(true)
    .enableInteractionAutoCapture(true)
    .enableInteractionTextCapture(false)
    .enableInteractionAccessibilityLabelCapture(false)
)
NoteAuto Capture automatically tracks screen views and ignores manually sent screen events while enabled.If you are migrating from manual screen tracking, please review the migration guide here.

UIKit Capture

UIKit screen capture is handled by view controller lifecycle callbacks.

Screen Tracking

By default, only direct children of UINavigationController, UITabBarController, UISplitViewController, and UIPageViewController are treated as screens. Override isUserpilotContainerClass when a custom container’s children should be treated as screens:
extension SignUpFlowViewController {
    open override class var isUserpilotContainerClass: Bool {
        true
    }
}
Override userpilotScreenName to use a stable, meaningful name instead of the view controller class name:
extension MyViewController {
    open override var userpilotScreenName: String? {
        "Main Signup Flow"
    }
}
Override userpilotScreenTitle to supply a custom title or disable title capture for that screen:
extension PrivateViewController {
    open override var userpilotScreenTitle: String? {
        nil // Disable title capture for this screen
    }
}
Use userpilotIgnoreScreen when a view controller is auxiliary content that should not emit its own automatic screen-view event:
extension SplashViewController {
    open override var userpilotIgnoreScreen: Bool {
        true
    }
}

Screen Events

When screen auto capture is enabled, the SDK publishes a screen event the first time a UIViewController appears (pushed, presented, or shown in a tab). Each screen event carries the current screen’s metadata only — there is no previous screen, path, timestamp, or root-flag field on the event itself.
PropertyTypeAlways sent?Notes
screen_namestringyesResolved screen name (userpilotScreenName override, SwiftUI userpilotScreenName(_:) modifier, or the VC class name).
screen_classstringyesThe view controller’s class name.
screen_typestringyesE.g. "UIViewController" or "NavigationController".
is_userpilot_container_classboolyestrue for built-in container VCs and any custom container that overrides isUserpilotContainerClass.
sourcestringyesAlways "auto-capture" for auto captured screens.
navigation_titlestringoptionalOnly included when present and non-empty.
vc_accessibility_identifierstringoptionalOnly included when set on the VC’s view.
vc_accessibility_labelstringoptionalOnly included when set on the VC’s view.
screen_name_matches_previous_screenbooloptionalSwiftUI only — emitted when the resolved name matches the previous screen.
ui_frameworkstringoptional"UIKit" or "SwiftUI", set from Config.appFramework.
UIAlertController is not tracked as a screen. Its presentation is routed to the dialog auto capture path and emitted as a mobile_autocapture interaction with interaction_type = "view_presented" carrying the alert’s title and message.

Interaction Events

When interaction auto capture is enabled, the SDK captures:
Interaction typeUIKit elementNotes
TapUIButton, other UIControl subclassesIncludes target-action name when available
Value changeUISwitch, UISlider, UISegmentedControl, UIStepper, UIDatePicker, UIPageControl, UIPickerViewSlider values are cached and sent once per screen to avoid event floods
Text inputUITextField, UITextViewCached; one event per field when leaving the screen
Cell selectionUITableView, UICollectionViewCell class name, index path, and visible text/labels when available
View tapUILabel, UIImageView, custom viewsResolves deepest subview at touch point for accurate element type and text
Tab selectionUITabBarControllerEmitted as interaction_type = "tab_selected" with tab_name and tab_index
Dialog presentationUIAlertController (alert + action sheet styles)Emitted as interaction_type = "view_presented" with the alert’s title and message

UIKit Privacy Helpers

Use Userpilot.Config to disable text, accessibility label, or screen title capture globally. Because accessibility labels can mirror on-screen text when accessibility features are on, disable enableInteractionAccessibilityLabelCapture when using enableInteractionTextCapture(false). Use these helpers for UIKit-specific privacy and payload control:
  • userpilotRedactText — replace captured text with ****
  • userpilotRedactAccessibilityLabel — replace captured accessibility labels with ****
  • userpilotIgnoreInteractions — skip interaction capture for a responder subtree
  • userpilotIgnoreInnerHierarchy — attribute child taps to the container and hide inner details while still recording that an interaction happened
passwordTextField.userpilotRedactText = true
sensitiveButton.userpilotRedactAccessibilityLabel = true
pinContainerView.userpilotIgnoreInteractions = true
pinPadView.userpilotIgnoreInnerHierarchy = true
Redaction is recursive: set it on a container to apply to all descendants. You can also set Userpilot Redact Text and Userpilot Redact Accessibility Label in Interface Builder under the Responder section of the Attributes Inspector. Unlike userpilotIgnoreInteractions (which records nothing), userpilotIgnoreInnerHierarchy still sends a tap event — it just omits inner details. Class-level defaults are available via overridable class properties:
class PaymentCellView: UIView {
    override class var userpilotIgnoreInteractionsDefault: Bool { true }
}

class SensitiveContainerView: UIView {
    override class var userpilotIgnoreInnerHierarchyDefault: Bool { true }
}
A per-instance value always takes precedence over the class default.

Custom Clickable Views

Standard controls (UIButton, UIControl subclasses, table/collection cells) and tappable views in the normal touch chain are captured automatically. For custom UIKit views that act as buttons but are not recognized, call:
customButtonView.userpilotRecognizeClickAnalytics()
This enables user interaction, adds a tap gesture if needed, sets button-like accessibility traits, and marks the view so the SDK can record taps. You do not need this for UIButton, other UIControl subclasses, or views that already receive touches in the responder chain. For SwiftUI, the equivalent API is userpilotLabel(_:) — it tags any SwiftUI view (including composite ones using .onTapGesture) with a stable analytics label and view type. See Labeling Custom SwiftUI Views below.

SwiftUI Capture

SwiftUI interaction capture is emitted through the hosting controller and routed through the same auto capture pipeline as UIKit capture. SwiftUI exposes a small set of view modifiers that mirror the UIKit auto capture surface.
ModifierPurpose
userpilotScreenName(_:)Override the screen name reported for the auto captured screen this view belongs to.
userpilotScreen(_:)Emit a manual screen event when this view appears (for apps with automatic screen capture disabled).
userpilotLabel(_:)Attach a stable analytics label and logical view type to a view (recommended for custom or composite tappable views).
userpilotRedactText(_:)Mark text content as sensitive — captured text becomes ****.
userpilotIgnoreInteractions(_:)Suppress all interaction events for this view and its descendants.
All UIKit configuration options — including enableInteractionTextCapture, enableInteractionAccessibilityLabelCapture, enableInteractionValueCapture, and enableScreenTitleCapture — apply unchanged to SwiftUI apps. Captured SwiftUI interactions include:
  • TextField and TextEditor text changes.
  • Toggle, slider, stepper, and picker value changes
  • List row and inline/wheel picker selections
  • Most views with .onTapGesture — observed through the same UIWindow swizzle the SDK uses for UIKit

SwiftUI Screen Tracking

With enableScreenAutoCapture(true), the SDK records a screen event whenever a SwiftUI view becomes visible inside its UIHostingController. By default the screen name is derived from the SwiftUI type. Use userpilotScreenName(_:) to override the auto captured screen name with a stable, human-readable string. The modifier propagates the name to the underlying hosting controller, so it takes effect on the next automatic screen event for that screen. This is the right choice when screen auto capture is enabled and you want a better label than the synthesized SwiftUI type name. Use userpilotScreen(_:) when screen auto capture is disabled (or when you need to emit an additional screen event for a non-routing view such as a tab). It calls Userpilot.screen(_:) from .onAppear. If name is omitted the SwiftUI type name is used.
struct ProfileView: View {
    var body: some View {
        VStack {
            Text("Profile")
        }
        .userpilotScreenName("User Profile")
    }
}
NoteIf enableScreenAutoCapture is true, manual Userpilot.screen(_:) calls (and therefore userpilotScreen(_:)) are intentionally suppressed to avoid double-tracking. Pick one of the two modifiers per screen.

Labeling Custom SwiftUI Views

The recommended way to make a custom or composite SwiftUI view identifiable in analytics is userpilotLabel(_:). The modifier sets a stable label and a logical view type (Button, Text, Toggle, NavigationLink, or the SwiftUI type name) on the resolved underlying UIKit view. The auto capture pipeline reads those values and reports them as element_text on every interaction event the view emits.
// Composite tappable card — the entire HStack is one logical "Favorite row"
HStack {
    Image(systemName: "star")
    Text("Favorite")
}
.onTapGesture { toggleFavorite() }
.userpilotLabel("Favorite row")

// Make a Button report a friendlier name
Button("Submit") { send() }
    .userpilotLabel("Submit order")

// Override what an isolated Text reports when it participates in tap events
Text("Balance")
    .userpilotLabel("Account balance label")

SwiftUI Privacy Helpers

Use userpilotRedactText(_:) to mark text content under a view as sensitive. Captured element_text becomes ****; the on-screen text is unchanged. The flag propagates down the responder chain, so applying it to a container redacts every descendant.
Text("Account: \(accountNumber)")
    .userpilotRedactText(true)

VStack {
    Text("Balance: $\(balance)")
    Text("Account: \(accountNumber)")
}
.userpilotRedactText(true)
SwiftUI does not currently expose a dedicated modifier for redacting accessibility labels. Use the global enableInteractionAccessibilityLabelCapture config to disable accessibility-label capture process-wide, or attach userpilotRedactAccessibilityLabel = true to the underlying UIKit view via a UIViewRepresentable if you need per-view control. Use userpilotIgnoreInteractions(_:) to stop the SDK from emitting any interaction events for a view and its descendants. The view itself stays fully functional.
Button("Debug Action", action: debugAction)
    .userpilotIgnoreInteractions(true)

Section {
    Toggle("Debug Mode", isOn: $debugMode)
    Button("Clear Cache") { clearCache() }
}
.userpilotIgnoreInteractions(true)
This applies to the underlying SwiftUI subtree only. Interactions in pushed/presented hosting controllers are not affected.

Runtime Controls

These APIs stop or resume auto capture:
Userpilot.stopAutoCapture()

// ... sensitive flow ...

Userpilot.resumeAutoCapture()
While stopped, no screen or interaction events are recorded regardless of other configuration. This is a global toggle that overrides per-view settings.