Skip to content

tvOS Guide#

This page covers tvOS integration for Storyteller. Use the iOS guide only for iOS apps.

Quickstart#

Before adding Storyteller to your tvOS app, make sure the app has:

  1. A Storyteller API key for your tenant.
  2. Xcode 15 or later.
  3. A tvOS deployment target of 15.0 or later.

The SDK supports Swift concurrency initialization on tvOS and is included in the same distribution channels as iOS.

Prerequisites#

  • Add the API key for your tenant.
  • Ensure your app target and extensions are configured for tvOS 15.0+.
  • Confirm Storyteller initialization is performed before opening Storyteller views.

Installation#

Swift Package Manager#

The SDK can be added as a Swift package dependency.

  1. In Xcode, open File > Add Packages….
  2. Add https://github.com/getstoryteller/storyteller-sdk-swift-package.

CocoaPods#

The tvOS SDK can be added with the same CocoaPods source and pod setup used on iOS.

source 'https://github.com/getstoryteller/storyteller-sdk-ios-podspec.git'
source 'https://github.com/CocoaPods/Specs.git'

use_frameworks!

target 'MyTVApp' do
   # Pods for MyTVApp
   pod 'StorytellerSDK'
end

XCFrameworks#

XCFrameworks from Storyteller releases include tvOS and can be used by tvOS apps when needed.

  1. Download XCFrameworks from the iOS Quickstart Guide.
  2. Add the frameworks to your tvOS target.
  3. Add Lottie 4.5.2 or later, below 5.0, to the same tvOS target when integrating manually. CocoaPods and Swift Package Manager integrations add this automatically, but manual XCFramework integrations must embed it explicitly.
  4. Enable Embed & Sign for each Storyteller xcframework where required.

SDK Initialization#

Initialize Storyteller before rendering Storyteller rows or opening Player views.

import StorytellerSDK

let userInput = StorytellerUserInput(externalId: "user-id")

Task {
    do {
        try await Storyteller.shared.initialize(
            apiKey: "[APIKEY]",
            userInput: userInput
        )
    } catch {
        print("Storyteller init failed: \(error)")
    }
}

Rows configuration (Clips and Stories)#

Use separate models for Clips and Stories, then render both rows in your view hierarchy.

import StorytellerSDK
import SwiftUI

struct HomeView: View {
    @State private var clipsModel: StorytellerClipsListModel
    @State private var storiesModel: StorytellerStoriesListModel

    init() {
        let theme: StorytellerTheme = {
            var theme = StorytellerTheme()
            theme.light.tiles.title.textSize = 20
            theme.dark = theme.light
            return theme
        }()

        _clipsModel = State(
            initialValue: StorytellerClipsListModel(
                configuration: StorytellerClipsListConfiguration(collectionId: "row-paging", theme: theme)
            )
        )
        _storiesModel = State(
            initialValue: StorytellerStoriesListModel(
                configuration: StorytellerStoriesListConfiguration(categories: ["automation"], theme: theme)
            )
        )
    }

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 24) {
                Text("Clips")
                StorytellerClipsRow(model: clipsModel)
                    .frame(height: 400)

                Text("Stories")
                StorytellerStoriesRow(model: storiesModel)
                    .frame(height: 400)
            }
            .padding(24)
        }
    }
}

When the current user changes, reinitialize with the new user and call reloadData() on each model to refresh content.

Programmatic playback#

To open a specific Story programmatically from your app UI:

import SwiftUI
import StorytellerSDK

struct PlaybackActionView: View {
    var body: some View {
        Button("Open latest Story") {
            Task {
                do {
                    try await Storyteller.shared.openStory(externalId: "story-external-id")
                } catch {
                    // handle playback launch error
                    print("Unable to open Story: \(error)")
                }
            }
        }
    }
}

tvOS-Specific Behavior#

  • Focus and navigation are optimized for Apple TV remote interactions.
  • Storyteller uses the same public integration APIs on tvOS, with platform-appropriate behavior for Player and row interactions.
  • Continuous Clips playback is controlled by your CMS collection configuration. When enabled in CMS, Clips auto-advance to the next item.

Troubleshooting#

  • Initialization timing: call Storyteller.shared.initialize(...) before any Storyteller view is presented.
  • Blank or stuck rows: confirm Storyteller.shared.initialize(...) succeeds and reloadData() is called after user/session changes.
  • Playback open failures: ensure Story/Clip IDs are valid for your tenant and the app key matches the target environment.
  • Focus/remote behavior: if focus does not move as expected, verify the surrounding view hierarchy and spacing, and keep enough focusable elements on screen.

Caveats and Limitations#

  • Avoid custom host-level focus overrides around Storyteller rows and Player views, as they can interfere with expected focus movement.