Skip to content

Embedded Clips#

Use Embedded Clips when you want the full Clips Player inside your own view hierarchy rather than presenting it programmatically. UIKit apps use StorytellerClipsViewController; SwiftUI apps use StorytellerClipsView with a StorytellerClipsModel.

If you need Clip tiles in a row or grid rather than an embedded Player, use Storyteller List Views.

topLevelBackButtonEnabled defaults to false in both framework routes.

Adding it in the UI#

Place StorytellerClipsViewController inside a UITabBarController:

let embeddedClipsVC = StorytellerClipsViewController()
let tabBarVC = UITabBarController()
let otherVC = UIViewController()
tabBarVC.setViewControllers([embeddedClipsVC, otherVC], animated: false)

Or add it to a custom view-controller hierarchy:

final class ClipsHostViewController: UIViewController {
    private let embeddedClipsVC = StorytellerClipsViewController()

    override func viewDidLoad() {
        super.viewDidLoad()

        addChild(embeddedClipsVC)
        view.addSubview(embeddedClipsVC.view)
        // Add Auto Layout constraints here.
        embeddedClipsVC.didMove(toParent: self)
    }
}

StorytellerClipsViewController exposes:

  • topLevelBackButtonEnabled - whether the back button should be displayed when the player is at the top level of the collection.
  • canGoBack - whether the SDK's internal navigation stack has a previous category screen to pop.
  • delegate - the weak StorytellerClipsViewControllerDelegate used for loading and navigation callbacks.

For a UIKit integration using CocoaPods, see EmbeddedClipsViewController.

Create and retain a StorytellerClipsModel, then pass it to StorytellerClipsView. Handle actions in the view callback rather than assigning a delegate.

import SwiftUI
import StorytellerSDK

@available(iOS 14.0, *)
struct ExampleSwiftUIView: View {
    @Environment(\.presentationMode) private var presentationMode
    @StateObject var model = StorytellerClipsModel(
        configuration: StorytellerClipCollectionConfiguration(collectionId: "test-collection"),
        topLevelBackButtonEnabled: true
    )

    var body: some View {
        StorytellerClipsView(model: model) { action in
            if case .onTopLevelBackTapped = action {
                presentationMode.wrappedValue.dismiss()
            }
        }
    }
}

StorytellerClipsModel exposes the shared configuration, topLevelBackButtonEnabled, and canGoBack values, plus reloadData(), willShow(), and willHide().

For a SwiftUI integration with configuration, reload cadence, and context injection, see the Showcase implementation in ClipsViewModel and ClipsView.

Back navigation#

canGoBack is a read-only capability query; it does not perform navigation and it does not describe the host app's navigation stack. When it is true, the SDK owns navigation back from the current nested category screen. At the collection root it is false, even if the host app itself has somewhere to navigate back to.

To expose host-owned back navigation at the collection root, set topLevelBackButtonEnabled to true, then handle the callback for your framework:

Handle topLevelBackTapped() on the delegate. The default implementation for delegates that are also UIViewController instances pops the nearest host navigation controller. Override it for a custom route:

import StorytellerSDK
import UIKit

final class ClipsHostViewController: UIViewController, StorytellerClipsViewControllerDelegate {
    func topLevelBackTapped() {
        navigationController?.popViewController(animated: true)
    }

    func onDataLoadStarted() {}

    func onDataLoadComplete(success: Bool, error: Error?) {}
}

Handle .onTopLevelBackTapped in the StorytellerClipsView action closure and perform host-owned navigation, as shown in the SwiftUI setup above. model.canGoBack has the same root and nested behavior as the UIKit property.

In SDK versions 10.6.0 through 11.5.1, canGoBack returned the inverse result because of a regression. Version 11.6.0 restores the contract described above. If your integration negated canGoBack as a workaround, remove that negation when upgrading.

Loading Clips#

Use StorytellerClipCollectionConfiguration when configuring an embedded or programmatically presented Clip Player. Use StorytellerClipsListConfiguration only for Clip row and grid tile views such as StorytellerClipsRowView and StorytellerClipsGridView. The types are not interchangeable: list configuration controls tile layout and limits, while collection configuration controls Player destination, presentation theme, analytics context, and Ad placements.

StorytellerClipCollectionConfiguration has the following properties:

  • collectionId - the id of the collection to be displayed.
  • destination - specify which clip or category to show when opening the collection. If clip or category is not specified or wrong value is set, we default to the first clip.
  • theme - This property defines the overall appearance of the StorytellerClipsViewController. If this property is not specified, the global theme will be applied by default.
  • openReason - Action type used for analytics to track why the collection was opened. If nil, the open reason is handled internally.
  • context - Optional context data that will be included in analytics callbacks for attribution.
  • adConfiguration - Optional per-presentation Ad placement controls. When omitted or set to nil, opening pre-roll and bottom banner Ads are not opted in locally. Pass StorytellerClipsAdConfiguration to opt into individual placements when the tenant is remotely configured for the matching Clips Ad placement.

Apply the shared configuration through the route for your framework:

Call configure(with:) on the embedded view controller:

let embeddedClipsVC = StorytellerClipsViewController()
let configuration = StorytellerClipCollectionConfiguration(collectionId: "top-plays")
embeddedClipsVC.configure(with: configuration)

Calling configure(with:) does not reload data when collectionId and destination are unchanged. Call reloadData() to force a refresh.

Supply the configuration when creating StorytellerClipsModel. Update the model or call its methods to control the existing embedded Player:

let model = StorytellerClipsModel(
    configuration: StorytellerClipCollectionConfiguration(collectionId: "collection-id"),
    topLevelBackButtonEnabled: true
)

let configuration = model.configuration
let showsTopLevelBackButton = model.topLevelBackButtonEnabled
let canGoBack = model.canGoBack

model.reloadData()
model.willShow()
model.willHide()

SwiftUI reloads when the supplied model changes. Call model.reloadData() to refresh without changing its configuration.

To opt an embedded Clips presentation into opening pre-roll Ads, set the placement on the shared configuration:

let preRollConfiguration = StorytellerClipCollectionConfiguration(
    collectionId: "top-plays",
    adConfiguration: StorytellerClipsAdConfiguration(preRollEnabled: true)
)

To opt an embedded Clips presentation into bottom banner Ads:

let bottomBannerConfiguration = StorytellerClipCollectionConfiguration(
    collectionId: "top-plays",
    adConfiguration: StorytellerClipsAdConfiguration(bottomBannerEnabled: true)
)

Supply either configuration to the UIKit view controller or SwiftUI model. Local opt-in does not replace the matching tenant and provider configuration described in Ads.

Possible values for StorytellerClipCollectionConfiguration.Destination are:

let categoryDestination: StorytellerClipCollectionConfiguration.Destination = .category(id: "category-id")
let clipDestination: StorytellerClipCollectionConfiguration.Destination = .clip(id: "clip-id")

An invalid or unavailable destination falls back to the first Clip.

Auto-pause#

Besides when the user pauses the Clips Player, the SDK automatically pauses or plays based on visibility changes, such as backgrounding the app or switching tabs. If you have a scenario that is not covered, such as displaying a modal over the Player, send visibility hints through the UIKit view controller or SwiftUI model:

  • willHide() - Call this before the Player becomes obscured or inactive.
  • willShow() - Call this when the Player becomes visible or active again.

Note: Auto-pausing respects the user's explicit pause state. If the user manually paused, willShow() will not automatically resume playback.

ReloadData#

Call reloadData() on the UIKit view controller or SwiftUI model to trigger a new request and reset the Player to its initial state.

Note: If category filters are currently applied when reloadData() is called, the player will navigate back one level in the category stack instead of reloading the entire collection. If no category filters are applied, it reloads the top-level collection data.

Delegate#

Use the callback mechanism for your framework:

Event UIKit SwiftUI
Loading starts StorytellerClipsViewControllerDelegate.onDataLoadStarted() .onDataLoadStarted action
Loading completes StorytellerClipsViewControllerDelegate.onDataLoadComplete(success:error:) .onDataLoadComplete(success:error:) action
Top-level back tapped StorytellerClipsViewControllerDelegate.topLevelBackTapped() .onTopLevelBackTapped action

The view controller holds its delegate weakly, so retain the delegate in your app. SwiftUI receives the same events through the StorytellerClipsView action closure shown above.

Safe Area / Inset Management#

Constrain the UIKit view controller or SwiftUI wrapper to a non-zero area that respects the host safe area. This prevents Player controls from being obscured by navigation, status, or tab bars.

Continue Your Integration#

  • See Custom Themes for Embedded Clips appearance and spacing.
  • Add context to the shared collection configuration for Analytics attribution.
  • See Ads before enabling opening pre-roll or bottom banner placements.