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

# Page Tracking

> Track page views and user engagement with PageTracker

`PageTracker` is the recommended approach for tracking user interactions in your iOS and tvOS apps. It automatically generates `Pageview` events, measures engagement time, and enables contextual cohorts when URLs are provided.

<CardGroup cols={3}>
  <Card title="Lifecycle" href="#pagetracker-lifecycle" icon="arrows-rotate" />

  <Card title="Events" href="#tracking-events" icon="bolt" />

  <Card title="Engagement" href="#engagement-tracking" icon="chart-line" />
</CardGroup>

## Overview

`PageTracker` provides:

* **Automatic `Pageview` events** on first resume
* **Engagement time tracking** between resume and pause
* **`PageViewComplete` events** when stopped
* **Contextual cohort generation** when URLs are provided
* **Linked event tracking** with consistent view IDs

<Warning>
  **Only one PageTracker (or MediaTracker) can be active at a time.** Creating a new tracker automatically stops any existing one.
</Warning>

## Creating a PageTracker

<CodeGroup>
  ```swift Swift theme={"dark"}
  import Permutive_iOS

  class ArticleViewController: UIViewController {
      private var pageTracker: PageTrackerProtocol?

      override func viewDidLoad() {
          super.viewDidLoad()

          // Create event properties (optional)
          let properties = try? EventProperties([
              "category": "sports",
              "subcategory": "football",
              "author": "Jane Smith",
              "premium": true
          ])

          // Create context with page information
          let context = Context(
              title: "Match Preview: Team A vs Team B",
              url: URL(string: "https://example.com/sports/match-preview"),
              referrer: URL(string: "https://example.com/sports")
          )

          // Create the PageTracker
          pageTracker = try? Permutive.shared.createPageTracker(
              properties: properties,
              context: context
          )
      }
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  #import "ArticleViewController.h"
  @import Permutive_iOS;

  @interface ArticleViewController ()
  @property (nonatomic, strong) NSObject<PermutivePageTrackerProtocol> *pageTracker;
  @end

  @implementation ArticleViewController

  - (void)viewDidLoad {
      [super viewDidLoad];

      // Create event properties
      NSError *error = nil;
      PermutiveEventProperties *properties = [[PermutiveEventProperties alloc]
          init:@{
              @"category": @"sports",
              @"subcategory": @"football",
              @"author": @"Jane Smith",
              @"premium": @YES
          }
          error:&error];

      // Create context
      PermutiveContext *context = [[PermutiveContext alloc]
          initWithTitle:@"Match Preview: Team A vs Team B"
          url:[NSURL URLWithString:@"https://example.com/sports/match-preview"]
          referrer:[NSURL URLWithString:@"https://example.com/sports"]];

      // Create the PageTracker
      self.pageTracker = [Permutive.shared
          createPageTrackerWithProperties:properties
          context:context
          error:&error];
  }

  @end
  ```
</CodeGroup>

## PageTracker Lifecycle

### State Diagram

```
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  Created ──► resume() ──► Running ──► pause() ──► Paused    │
│                              │                      │        │
│                              │                      │        │
│                              ▼                      ▼        │
│                           stop() ◄──────────────────┘        │
│                              │                               │
│                              ▼                               │
│                          Stopped                             │
│                                                              │
└──────────────────────────────────────────────────────────────┘
```

### Lifecycle Methods

| Method     | Effect                    | Events Generated             |
| ---------- | ------------------------- | ---------------------------- |
| `resume()` | Starts/resumes tracking   | `Pageview` (first call only) |
| `pause()`  | Pauses engagement timer   | None                         |
| `stop()`   | Ends tracking permanently | `PageViewComplete`           |

### UIViewController Integration

<CodeGroup>
  ```swift Swift theme={"dark"}
  class ArticleViewController: UIViewController {
      private var pageTracker: PageTrackerProtocol?

      override func viewDidLoad() {
          super.viewDidLoad()

          let context = Context(
              title: "Article Title",
              url: URL(string: "https://example.com/article"),
              referrer: nil
          )

          pageTracker = try? Permutive.shared.createPageTracker(
              properties: nil,
              context: context
          )
      }

      override func viewDidAppear(_ animated: Bool) {
          super.viewDidAppear(animated)
          // Start or resume tracking
          try? pageTracker?.resume()
      }

      override func viewDidDisappear(_ animated: Bool) {
          super.viewDidDisappear(animated)
          // Pause when view is not visible
          pageTracker?.pause()
      }

      deinit {
          // Stop tracking when view is deallocated
          pageTracker?.stop()
      }
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  @implementation ArticleViewController

  - (void)viewDidLoad {
      [super viewDidLoad];

      PermutiveContext *context = [[PermutiveContext alloc]
          initWithTitle:@"Article Title"
          url:[NSURL URLWithString:@"https://example.com/article"]
          referrer:nil];

      self.pageTracker = [Permutive.shared
          createPageTrackerWithProperties:nil
          context:context
          error:nil];
  }

  - (void)viewDidAppear:(BOOL)animated {
      [super viewDidAppear:animated];
      [self.pageTracker resumeAndReturnError:nil];
  }

  - (void)viewDidDisappear:(BOOL)animated {
      [super viewDidDisappear:animated];
      [self.pageTracker pause];
  }

  - (void)dealloc {
      [self.pageTracker stop];
  }

  @end
  ```
</CodeGroup>

## SwiftUI Integration

For SwiftUI, use `onAppear` and `onDisappear` modifiers:

```swift theme={"dark"}
import SwiftUI
import Permutive_iOS

struct ArticleView: View {
    let article: Article
    @StateObject private var tracker = PageTrackerWrapper()

    var body: some View {
        ScrollView {
            ArticleContent(article: article)
        }
        .onAppear {
            tracker.start(
                title: article.title,
                url: URL(string: article.url)
            )
        }
        .onDisappear {
            tracker.stop()
        }
    }
}

class PageTrackerWrapper: ObservableObject {
    private var pageTracker: PageTrackerProtocol?

    func start(title: String, url: URL?) {
        let context = Context(title: title, url: url, referrer: nil)
        pageTracker = try? Permutive.shared.createPageTracker(
            properties: nil,
            context: context
        )
        try? pageTracker?.resume()
    }

    func stop() {
        pageTracker?.stop()
        pageTracker = nil
    }
}
```

## Tracking Events

Track additional events linked to the current page view:

<CodeGroup>
  ```swift Swift theme={"dark"}
  // Track a link click
  try? pageTracker?.track(
      event: "LinkClick",
      properties: try? EventProperties([
          "link_url": "https://example.com/related",
          "link_text": "Read More"
      ])
  )

  // Track a form submission
  try? pageTracker?.track(
      event: "FormSubmit",
      properties: try? EventProperties([
          "form_id": "newsletter_signup",
          "email_provided": true
      ])
  )

  // Track without properties
  try? pageTracker?.track(event: "ShareButton", properties: nil)
  ```

  ```objectivec Objective-C theme={"dark"}
  // Track a link click
  NSError *error = nil;
  PermutiveEventProperties *linkProps = [[PermutiveEventProperties alloc]
      init:@{@"link_url": @"https://example.com/related", @"link_text": @"Read More"}
      error:&error];
  [self.pageTracker trackWithEvent:@"LinkClick" properties:linkProps error:&error];

  // Track without properties
  [self.pageTracker trackWithEvent:@"ShareButton" properties:nil error:&error];
  ```
</CodeGroup>

<Info>
  Events tracked via `PageTracker.track()` include the same Context and view ID as the `Pageview` event, enabling linked analysis.
</Info>

## Engagement Tracking

### Scroll Depth

Track how much content users have viewed:

<CodeGroup>
  ```swift Swift theme={"dark"}
  class ArticleViewController: UIViewController, UIScrollViewDelegate {
      private var pageTracker: PageTrackerProtocol?
      @IBOutlet weak var scrollView: UIScrollView!

      override func viewDidLoad() {
          super.viewDidLoad()
          scrollView.delegate = self
          // Setup pageTracker...
      }

      func scrollViewDidScroll(_ scrollView: UIScrollView) {
          let contentHeight = scrollView.contentSize.height - scrollView.bounds.height
          guard contentHeight > 0 else { return }

          let percentage = Float(scrollView.contentOffset.y / contentHeight)
          let clampedPercentage = min(max(percentage, 0), 1)

          pageTracker?.updateViewed(percentage: clampedPercentage)
      }
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
      CGFloat contentHeight = scrollView.contentSize.height - scrollView.bounds.size.height;
      if (contentHeight <= 0) return;

      float percentage = scrollView.contentOffset.y / contentHeight;
      percentage = MIN(MAX(percentage, 0), 1);

      [self.pageTracker updateViewedWithPercentage:percentage];
  }
  ```
</CodeGroup>

### Engagement Time

Engagement time is automatically tracked:

* Timer starts when `resume()` is called
* Timer pauses when `pause()` is called
* Total engagement time sent with `PageViewComplete` event

## Context Object

The `Context` object provides page metadata:

| Property   | Type     | Description                           |
| ---------- | -------- | ------------------------------------- |
| `title`    | `String` | Page title                            |
| `url`      | `URL?`   | Page URL (enables contextual cohorts) |
| `referrer` | `URL?`   | Referring page URL                    |

<CodeGroup>
  ```swift Swift theme={"dark"}
  let context = Context(
      title: "Article: iOS Development",
      url: URL(string: "https://example.com/ios-development"),
      referrer: URL(string: "https://example.com/tutorials")
  )
  ```

  ```objectivec Objective-C theme={"dark"}
  PermutiveContext *context = [[PermutiveContext alloc]
      initWithTitle:@"Article: iOS Development"
      url:[NSURL URLWithString:@"https://example.com/ios-development"]
      referrer:[NSURL URLWithString:@"https://example.com/tutorials"]];
  ```
</CodeGroup>

<Tip>
  The `domain` is automatically inferred from the `url` if set. Always provide URLs to enable contextual cohort generation.
</Tip>

## Background Handling

The SDK automatically handles app backgrounding:

```swift theme={"dark"}
// When app enters background:
// - Active PageTracker is automatically stopped
// - PageViewComplete event is sent

// When resuming, create a new PageTracker if needed
func applicationWillEnterForeground() {
    if shouldResumeTracking {
        createAndResumePageTracker()
    }
}
```

## tvOS Considerations

<Info>
  **tvOS Note:** PageTracker works identically on tvOS. Use focus-based navigation events instead of touch-based scrolling for engagement tracking.
</Info>

```swift theme={"dark"}
// tvOS focus tracking example
func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
    // Track focus changes as navigation events
    if let focusedView = context.nextFocusedView {
        try? pageTracker?.track(
            event: "FocusChange",
            properties: try? EventProperties([
                "focused_element": String(describing: type(of: focusedView))
            ])
        )
    }
}
```

## Error Handling

<CodeGroup>
  ```swift Swift theme={"dark"}
  do {
      let pageTracker = try Permutive.shared.createPageTracker(
          properties: properties,
          context: context
      )
      try pageTracker.resume()
  } catch {
      print("PageTracker error: \(error)")
      // Handle error - tracking will be skipped
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  NSError *error = nil;
  NSObject<PermutivePageTrackerProtocol> *pageTracker = [Permutive.shared
      createPageTrackerWithProperties:properties
      context:context
      error:&error];

  if (error != nil) {
      NSLog(@"PageTracker creation error: %@", error);
      return;
  }

  [pageTracker resumeAndReturnError:&error];
  if (error != nil) {
      NSLog(@"PageTracker resume error: %@", error);
  }
  ```
</CodeGroup>

## Best Practices

<Tabs>
  <Tab title="Do">
    * Create PageTracker in `viewDidLoad`
    * Call `resume()` in `viewDidAppear`
    * Call `pause()` in `viewDidDisappear`
    * Call `stop()` in `deinit`
    * Always provide URLs for contextual targeting
    * Track meaningful events with descriptive names
    * Update scroll percentage as user scrolls
  </Tab>

  <Tab title="Don't">
    * Don't create multiple PageTrackers simultaneously
    * Don't call `resume()` after `stop()`
    * Don't forget to call `stop()` when done
    * Don't track too many fine-grained events
    * Don't include PII in event properties
  </Tab>
</Tabs>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Pageview event not firing">
    **Problem:** No Pageview event in logs.

    **Solutions:**

    * Ensure `resume()` is called after creating the PageTracker
    * Check that SDK is initialized before creating PageTracker
    * Verify no errors when creating PageTracker
    * Enable debug logging: `options.logModes = LogMode.all`
  </Accordion>

  <Accordion title="Previous PageTracker stopped unexpectedly">
    **Problem:** PageTracker stops when navigating.

    **Cause:** Creating a new PageTracker stops any existing one.

    **Solution:** This is expected behavior. Only one tracker can be active. Stop the previous tracker explicitly if needed, or let the new one replace it.
  </Accordion>

  <Accordion title="Events rejected">
    **Problem:** Events tracked via PageTracker are rejected.

    **Solutions:**

    * Check event names match your schema
    * Verify property names and types
    * Enable debug logging to see schema errors
  </Accordion>
</AccordionGroup>

## Related Documentation

<CardGroup cols={2}>
  <Card title="Event Tracking" icon="bolt" href="/sdks/mobile/ios/features/event-tracking">
    Track custom events
  </Card>

  <Card title="Contextual Data" icon="bullseye" href="/sdks/mobile/ios/core-concepts/contextual-data">
    Content-based targeting
  </Card>

  <Card title="Google Ad Manager" icon="google" href="/sdks/mobile/ios/integrations/google-ad-manager">
    Ad targeting with PageTracker
  </Card>

  <Card title="Event Properties" icon="list" href="/sdks/mobile/ios/core-concepts/event-properties">
    Structuring event data
  </Card>
</CardGroup>
