Storyteller delivers user activity events to your app through StorytellerDelegate.onUserActivityOccurred(type:data:). This guide shows how to retain the delegate, select the tracking behavior during initialization, forward events to your analytics layer, and add host-defined attribution context.
Use the Analytics Event Reference after setup to choose the event types and payload fields your analytics implementation needs.
Storyteller.shared.delegate is weak. Keep the delegate in app-owned state for as long as you need callbacks, and assign the same object that handles your other global Storyteller callbacks. If another part of the app later replaces Storyteller.shared.delegate, the original object stops receiving events.
The following complete example forwards the serialized event key (type.rawValue), selected content identifiers, and any analytics context into a provider-independent host analytics layer. Replace ConsoleAnalytics with your own analytics adapter.
importStorytellerSDKstructHostAnalyticsEvent{letname:StringletstoryId:String?letclipId:String?letcontext:StorytellerAnalyticsContext?}protocolHostAnalyticsTracking:AnyObject{functrack(_event:HostAnalyticsEvent)}finalclassConsoleAnalytics:HostAnalyticsTracking{functrack(_event:HostAnalyticsEvent){print("Storyteller event: \(event.name), "+"story: \(event.storyId??"none"), "+"clip: \(event.clipId??"none"), "+"context: \(event.context??[:])")}}finalclassStorytellerAnalyticsDelegate:StorytellerDelegate{privateletanalytics:anyHostAnalyticsTrackinginit(analytics:anyHostAnalyticsTracking){self.analytics=analytics}funconUserActivityOccurred(type:StorytellerUserActivity.EventType,data:StorytellerUserActivityData){analytics.track(HostAnalyticsEvent(name:type.rawValue,storyId:data.storyId,clipId:data.clipId,context:data.context))}}@MainActorfinalclassStorytellerIntegration{privateletstorytellerDelegate:StorytellerAnalyticsDelegateinit(analytics:anyHostAnalyticsTracking){letdelegate=StorytellerAnalyticsDelegate(analytics:analytics)storytellerDelegate=delegateStoryteller.shared.delegate=delegate}funcinitialize(apiKey:String,userId:String,trackingOptions:StorytellerEventTrackingOptions)asyncthrows{tryawaitStoryteller.shared.initialize(apiKey:apiKey,userInput:StorytellerUserInput(externalId:userId),eventTrackingOptions:trackingOptions)}}@MainActorfinalclassAppServices{privateletstoryteller=StorytellerIntegration(analytics:ConsoleAnalytics())funcstart()asyncthrows{// Use .enableAll only when it matches your app's consent policy.tryawaitstoryteller.initialize(apiKey:"your-api-key",userId:"your-user-id",trackingOptions:.enableAll)}}
StorytellerUserActivityData is an event-specific payload, so most properties are optional. Forward only the fields your analytics contract needs, using the event reference to determine which fields apply to each event. The Showcase app demonstrates provider-specific mapping in StorytellerTrackingDelegate.
Pass StorytellerEventTrackingOptions when you call initialize(...). The value is fixed for that initialization; reinitialize the SDK to apply a later consent change.
For host analytics delivery, these options have distinct effects:
Option
Effect on onUserActivityOccurred
enableUserActivityTracking
Must be enabled for the integrating app to receive user activity events.
enableAdTracking
Must also be enabled for Ad-related user activity events. It does not control whether a host-supplied Ad loading callback is requested.
enableStorytellerTracking
Controls Storyteller's own analytics collection; enableUserActivityTracking remains the host callback gate.
enableFullVideoAnalytics
When disabled, callbacks still arrive, but content identifiers and titles listed in Privacy and Tracking are removed from their payloads.
The other options affect personalization, viewing state, and functional behavior. Choose the complete configuration from your app's consent requirements; see Privacy and Tracking before changing defaults.
StorytellerAnalyticsContext is a type alias for [String: String]. The SDK does not prescribe its keys. Add a context dictionary to the configuration for the surface or presentation you want to attribute:
StorytellerStoriesListConfiguration
StorytellerClipsListConfiguration
StorytellerClipCollectionConfiguration, including UIKit and SwiftUI Embedded Clips
StorytellerCardConfiguration
StorytellerHomeConfiguration
For example, an Embedded Clips configuration can identify both its screen and placement:
The SDK carries the dictionary into StorytellerUserActivityData.context when an event can be attributed to that configured surface or to content opened from it. The property remains optional: events without an attributable configured surface do not receive a context value. Set context before loading or opening the content whose events you want to attribute.
Load known published content using a configuration with a distinctive context value.
Open or interact with that content to produce a documented event, such as openedStory or openedClip.
Confirm your analytics adapter receives the expected type.rawValue and payload.
Confirm data.context contains the value supplied by the originating configuration when that event is attributable to the surface.
Initialization success alone does not exercise this callback path or generate a host user activity callback. Trigger a supported content interaction when testing the integration.
Confirm the app still strongly retains its delegate and that Storyteller.shared.delegate has not been replaced.
Confirm enableUserActivityTracking was enabled during the current SDK initialization.
For an Ad event, also confirm enableAdTracking is enabled and that the corresponding Ad lifecycle point was actually reached.
Confirm the documented interaction occurred; loading and initialization callbacks are separate from user activity delivery.
If the callback arrives but data is missing:
Check enableFullVideoAnalytics before treating absent Story, Page, Clip, or Card identifiers and titles as an SDK fault.
For missing context, confirm the active configuration supplied it before the content was loaded or opened and that the event can be attributed to that surface.
Treat every event payload as event-specific; unrelated fields are expected to be nil.
Use the Analytics Event Reference for all public event keys, the common fields for each feature, event-specific fields, and enum value definitions.
{"slug": "analytics-integration", "page_title": "Integrate Analytics", "page_url": "AnalyticsIntegration/", "canonical_url": "/ios/AnalyticsIntegration/", "markdown": "# Integrate Analytics\n\nStoryteller delivers user activity events to your app through `StorytellerDelegate.onUserActivityOccurred(type:data:)`. This guide shows how to retain the delegate, select the tracking behavior during initialization, forward events to your analytics layer, and add host-defined attribution context.\n\nUse the [Analytics Event Reference](Analytics.md) after setup to choose the event types and payload fields your analytics implementation needs.\n\n## Retain a Delegate and Forward Events\n\n`Storyteller.shared.delegate` is weak. Keep the delegate in app-owned state for as long as you need callbacks, and assign the same object that handles your other global Storyteller callbacks. If another part of the app later replaces `Storyteller.shared.delegate`, the original object stops receiving events.\n\nThe following complete example forwards the serialized event key (`type.rawValue`), selected content identifiers, and any analytics context into a provider-independent host analytics layer. Replace `ConsoleAnalytics` with your own analytics adapter.\n\n<!-- storyteller-swift-example: id=analyticsintegration-forward-events target=sdk-ios context=file -->\n\n```swift\nimport StorytellerSDK\n\nstruct HostAnalyticsEvent {\n let name: String\n let storyId: String?\n let clipId: String?\n let context: StorytellerAnalyticsContext?\n}\n\nprotocol HostAnalyticsTracking: AnyObject {\n func track(_ event: HostAnalyticsEvent)\n}\n\nfinal class ConsoleAnalytics: HostAnalyticsTracking {\n func track(_ event: HostAnalyticsEvent) {\n print(\n \"Storyteller event: \\(event.name), \"\n + \"story: \\(event.storyId ?? \"none\"), \"\n + \"clip: \\(event.clipId ?? \"none\"), \"\n + \"context: \\(event.context ?? [:])\"\n )\n }\n}\n\nfinal class StorytellerAnalyticsDelegate: StorytellerDelegate {\n private let analytics: any HostAnalyticsTracking\n\n init(analytics: any HostAnalyticsTracking) {\n self.analytics = analytics\n }\n\n func onUserActivityOccurred(\n type: StorytellerUserActivity.EventType,\n data: StorytellerUserActivityData\n ) {\n analytics.track(\n HostAnalyticsEvent(\n name: type.rawValue,\n storyId: data.storyId,\n clipId: data.clipId,\n context: data.context\n )\n )\n }\n}\n\n@MainActor\nfinal class StorytellerIntegration {\n private let storytellerDelegate: StorytellerAnalyticsDelegate\n\n init(analytics: any HostAnalyticsTracking) {\n let delegate = StorytellerAnalyticsDelegate(analytics: analytics)\n storytellerDelegate = delegate\n Storyteller.shared.delegate = delegate\n }\n\n func initialize(\n apiKey: String,\n userId: String,\n trackingOptions: StorytellerEventTrackingOptions\n ) async throws {\n try await Storyteller.shared.initialize(\n apiKey: apiKey,\n userInput: StorytellerUserInput(externalId: userId),\n eventTrackingOptions: trackingOptions\n )\n }\n}\n\n@MainActor\nfinal class AppServices {\n private let storyteller = StorytellerIntegration(\n analytics: ConsoleAnalytics()\n )\n\n func start() async throws {\n // Use .enableAll only when it matches your app's consent policy.\n try await storyteller.initialize(\n apiKey: \"your-api-key\",\n userId: \"your-user-id\",\n trackingOptions: .enableAll\n )\n }\n}\n```\n\n`StorytellerUserActivityData` is an event-specific payload, so most properties are optional. Forward only the fields your analytics contract needs, using the event reference to determine which fields apply to each event. The Showcase app demonstrates provider-specific mapping in [`StorytellerTrackingDelegate`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.7.2/main/ShowcaseApp/Analytics/StorytellerTrackingDelegate.swift#L10).\n\n## Choose Tracking Options During Initialization\n\nPass `StorytellerEventTrackingOptions` when you call `initialize(...)`. The value is fixed for that initialization; reinitialize the SDK to apply a later consent change.\n\nFor host analytics delivery, these options have distinct effects:\n\n| Option | Effect on `onUserActivityOccurred` |\n| --- | --- |\n| `enableUserActivityTracking` | Must be enabled for the integrating app to receive user activity events. |\n| `enableAdTracking` | Must also be enabled for Ad-related user activity events. It does not control whether a host-supplied Ad loading callback is requested. |\n| `enableStorytellerTracking` | Controls Storyteller's own analytics collection; `enableUserActivityTracking` remains the host callback gate. |\n| `enableFullVideoAnalytics` | When disabled, callbacks still arrive, but content identifiers and titles listed in [Privacy and Tracking](PrivacyAndTracking.md#videos-tracking) are removed from their payloads. |\n\nThe other options affect personalization, viewing state, and functional behavior. Choose the complete configuration from your app's consent requirements; see [Privacy and Tracking](PrivacyAndTracking.md) before changing defaults.\n\n## Add Analytics Context\n\n`StorytellerAnalyticsContext` is a type alias for `[String: String]`. The SDK does not prescribe its keys. Add a context dictionary to the configuration for the surface or presentation you want to attribute:\n\n- `StorytellerStoriesListConfiguration`\n- `StorytellerClipsListConfiguration`\n- `StorytellerClipCollectionConfiguration`, including UIKit and SwiftUI Embedded Clips\n- `StorytellerCardConfiguration`\n- `StorytellerHomeConfiguration`\n\nFor example, an Embedded Clips configuration can identify both its screen and placement:\n\n<!-- storyteller-swift-example: id=analyticsintegration-embedded-clips-context target=sdk-ios context=statements -->\n\n```swift\nlet clipsConfiguration = StorytellerClipCollectionConfiguration(\n collectionId: \"top-plays\",\n context: [\n \"screen\": \"home\",\n \"placement\": \"primary-clips-feed\"\n ]\n)\n```\n\nThe SDK carries the dictionary into `StorytellerUserActivityData.context` when an event can be attributed to that configured surface or to content opened from it. The property remains optional: events without an attributable configured surface do not receive a context value. Set context before loading or opening the content whose events you want to attribute.\n\nSee [Context in the Analytics Event Reference](Analytics.md#context) for the complete attribution contract and another consumption example.\n\n## Verify the Integration\n\nAfter initialization succeeds:\n\n1. Load known published content using a configuration with a distinctive context value.\n1. Open or interact with that content to produce a documented event, such as `openedStory` or `openedClip`.\n1. Confirm your analytics adapter receives the expected `type.rawValue` and payload.\n1. Confirm `data.context` contains the value supplied by the originating configuration when that event is attributable to the surface.\n\nInitialization success alone does not exercise this callback path or generate a host user activity callback. Trigger a supported content interaction when testing the integration.\n\n## Troubleshoot Missing Events or Context\n\nIf no event arrives:\n\n- Confirm the app still strongly retains its delegate and that `Storyteller.shared.delegate` has not been replaced.\n- Confirm `enableUserActivityTracking` was enabled during the current SDK initialization.\n- For an Ad event, also confirm `enableAdTracking` is enabled and that the corresponding Ad lifecycle point was actually reached.\n- Confirm the documented interaction occurred; loading and initialization callbacks are separate from user activity delivery.\n\nIf the callback arrives but data is missing:\n\n- Check `enableFullVideoAnalytics` before treating absent Story, Page, Clip, or Card identifiers and titles as an SDK fault.\n- For missing context, confirm the active configuration supplied it before the content was loaded or opened and that the event can be attributed to that surface.\n- Treat every event payload as event-specific; unrelated fields are expected to be `nil`.\n\nUse [Callbacks or Analytics Events Do Not Arrive](Troubleshooting.md#callbacks-or-analytics-events-do-not-arrive) to separate analytics delivery from component loading, app navigation, and Ad loading callbacks.\n\n## Continue with the Event Reference\n\nUse the [Analytics Event Reference](Analytics.md) for all public event keys, the common fields for each feature, event-specific fields, and enum value definitions.\n", "copy_markdown_include_header": false, "base_path": "", "ai_dir": "ai", "missing_payload_behavior": "empty"}