> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userpilot.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Auto Capture

> Configure auto capture for the Userpilot iOS SDK

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.

```swift theme={null}
Userpilot(config: Userpilot.Config(token: "<APP_TOKEN>")
    .logging(enabled: true)
    .appFramework(.uiKit) // or .swiftUI
    .enableScreenAutoCapture(true)
    .enableInteractionAutoCapture(true)
    .enableInteractionTextCapture(true)
    .enableInteractionAccessibilityLabelCapture(true)
    .enableInteractionValueCapture(true)
)
```

| **Option**                                   | **Type**       | **Default** | **Description**                                                                                                                                   |
| -------------------------------------------- | -------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enableScreenAutoCapture`                    | `Bool`         | `false`     | Enables automatic screen tracking when view controllers or SwiftUI views appear.                                                                  |
| `enableInteractionAutoCapture`               | `Bool`         | `false`     | Enables automatic interaction capture for taps, value changes, text input, and selections.                                                        |
| `enableScreenTitleCapture`                   | `Bool`         | `true`      | If `false`, navigation and tab titles are not captured in screen payloads.                                                                        |
| `enableInteractionTextCapture`               | `Bool`         | `true`      | If `false`, user-visible text is redacted from captured interaction payloads.                                                                     |
| `enableInteractionAccessibilityLabelCapture` | `Bool`         | `true`      | If `false`, accessibility labels are not captured. Disable alongside `enableInteractionTextCapture(false)` when labels may mirror on-screen text. |
| `enableInteractionValueCapture`              | `Bool`         | `true`      | If `false`, values from controls such as switches, sliders, and pickers are not captured.                                                         |
| `ignoreTapForTextInputEditingActions`        | `Bool`         | `true`      | If `true`, prevents duplicate tap events for text input editing actions.                                                                          |
| `preferUIKitOverSwiftUIForNavigationBar`     | `Bool`         | `true`      | If `true`, prefers UIKit events over SwiftUI for navigation bar interactions to avoid duplicates.                                                 |
| `appFramework`                               | `AppFramework` | nil         | Framework hint: `.uiKit` or `.swiftUI`.                                                                                                           |

Example: disable all text and accessibility label capture for maximum privacy:

```swift theme={null}
Userpilot(config: Userpilot.Config(token: "<APP_TOKEN>")
    .appFramework(.uiKit)
    .enableScreenAutoCapture(true)
    .enableInteractionAutoCapture(true)
    .enableInteractionTextCapture(false)
    .enableInteractionAccessibilityLabelCapture(false)
)
```

<Tip>
  **Note**

  Auto 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](https://docs.userpilot.com/data-events/mobile-screen-tracking/mobile-screen-auto-capture#important).
</Tip>

***

## 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:

```swift theme={null}
extension SignUpFlowViewController {
    open override class var isUserpilotContainerClass: Bool {
        true
    }
}
```

Override `userpilotScreenName` to use a stable, meaningful name instead of the view controller class name:

```swift theme={null}
extension MyViewController {
    open override var userpilotScreenName: String? {
        "Main Signup Flow"
    }
}
```

Override `userpilotScreenTitle` to supply a custom title or disable title capture for that screen:

```swift theme={null}
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:

```swift theme={null}
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.

| **Property**                          | **Type** | **Always sent?** | **Notes**                                                                                                                |
| ------------------------------------- | -------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `screen_name`                         | string   | yes              | Resolved screen name (`userpilotScreenName` override, SwiftUI `userpilotScreenName(_:)` modifier, or the VC class name). |
| `screen_class`                        | string   | yes              | The view controller's class name.                                                                                        |
| `screen_type`                         | string   | yes              | E.g. `"UIViewController"` or `"NavigationController"`.                                                                   |
| `is_userpilot_container_class`        | bool     | yes              | `true` for built-in container VCs and any custom container that overrides `isUserpilotContainerClass`.                   |
| `source`                              | string   | yes              | Always `"auto-capture"` for auto captured screens.                                                                       |
| `navigation_title`                    | string   | optional         | Only included when present and non-empty.                                                                                |
| `vc_accessibility_identifier`         | string   | optional         | Only included when set on the VC's view.                                                                                 |
| `vc_accessibility_label`              | string   | optional         | Only included when set on the VC's view.                                                                                 |
| `screen_name_matches_previous_screen` | bool     | optional         | SwiftUI only — emitted when the resolved name matches the previous screen.                                               |
| `ui_framework`                        | string   | optional         | `"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 type** | **UIKit element**                                                                                          | **Notes**                                                                           |
| -------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Tap                  | `UIButton`, other `UIControl` subclasses                                                                   | Includes target-action name when available                                          |
| Value change         | `UISwitch`, `UISlider`, `UISegmentedControl`, `UIStepper`, `UIDatePicker`, `UIPageControl`, `UIPickerView` | Slider values are cached and sent once per screen to avoid event floods             |
| Text input           | `UITextField`, `UITextView`                                                                                | Cached; one event per field when leaving the screen                                 |
| Cell selection       | `UITableView`, `UICollectionView`                                                                          | Cell class name, index path, and visible text/labels when available                 |
| View tap             | `UILabel`, `UIImageView`, custom views                                                                     | Resolves deepest subview at touch point for accurate element type and text          |
| Tab selection        | `UITabBarController`                                                                                       | Emitted as `interaction_type = "tab_selected"` with `tab_name` and `tab_index`      |
| Dialog presentation  | `UIAlertController` (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

```swift theme={null}
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:

```swift theme={null}
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:

```swift theme={null}
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.

| **Modifier**                      | **Purpose**                                                                                                           |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `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.

<CodeGroup>
  ```swift Custom Screen Name theme={null}
  struct ProfileView: View {
      var body: some View {
          VStack {
              Text("Profile")
          }
          .userpilotScreenName("User Profile")
      }
  }
  ```

  ```swift Manual Screen Tracking theme={null}
  struct CheckoutView: View {
      var body: some View {
          VStack {
              Text("Checkout")
          }
          .userpilotScreen("Purchase Checkout")
      }
  }
  ```
</CodeGroup>

<Tip>
  **Note**

  If `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.
</Tip>

### 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.

```swift theme={null}
// 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.

```swift theme={null}
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.

```swift theme={null}
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:

```swift theme={null}
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.

<Frame>
  [**For any questions or concerns please reach out to support@userpilot.com**](mailto:support@userpilot.com)
</Frame>
