Skip to main content
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.

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
Only one PageTracker (or MediaTracker) can be active at a time. Creating a new tracker automatically stops any existing one.

Creating a PageTracker

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
        )
    }
}

PageTracker Lifecycle

State Diagram

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

Lifecycle Methods

MethodEffectEvents Generated
resume()Starts/resumes trackingPageview (first call only)
pause()Pauses engagement timerNone
stop()Ends tracking permanentlyPageViewComplete

UIViewController Integration

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()
    }
}

SwiftUI Integration

For SwiftUI, use onAppear and onDisappear modifiers:
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:
// 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)
Events tracked via PageTracker.track() include the same Context and view ID as the Pageview event, enabling linked analysis.

Engagement Tracking

Scroll Depth

Track how much content users have viewed:
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)
    }
}

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:
PropertyTypeDescription
titleStringPage title
urlURL?Page URL (enables contextual cohorts)
referrerURL?Referring page URL
let context = Context(
    title: "Article: iOS Development",
    url: URL(string: "https://example.com/ios-development"),
    referrer: URL(string: "https://example.com/tutorials")
)
The domain is automatically inferred from the url if set. Always provide URLs to enable contextual cohort generation.

Background Handling

The SDK automatically handles app backgrounding:
// 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

tvOS Note: PageTracker works identically on tvOS. Use focus-based navigation events instead of touch-based scrolling for engagement tracking.
// 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

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
}

Best Practices

  • 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

Troubleshooting

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