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:
- A Storyteller API key for your tenant.
- An Xcode installation. We test the current SDK line with Xcode 26.2; earlier Xcode versions may also work, but they are not part of our tested configuration.
- 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. The iOS Showcase applications have separate build requirements that do not change the tvOS deployment floor.
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.
- In Xcode, open File > Add Packages….
- Add
https://github.com/getstoryteller/storyteller-sdk-swift-package. - Let the package resolve
StorytellerLottie4.6.0 transitively; do not add upstream Airbnb Lottie separately.
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/getstoryteller/storyteller-lottie-ios-podspec.git'
source 'https://cdn.cocoapods.org/'
use_frameworks!
target 'MyTVApp' do
# Pods for MyTVApp
pod 'StorytellerSDK'
end
CocoaPods resolves StorytellerLottie 4.6.0 transitively from the StorytellerLottie spec repository.
XCFrameworks#
XCFrameworks from Storyteller releases include tvOS and can be used by tvOS apps when needed.
- Download XCFrameworks from the iOS Quickstart Guide.
- Download and unzip
StorytellerLottie4.6.0. - Add both
StorytellerSDK.xcframeworkandStorytellerLottie.xcframeworkto the same tvOS target. Do not add the upstream Airbnb Lottie framework: the SDK imports theStorytellerLottiemodule. - In the tvOS target's General > Frameworks, Libraries, and Embedded Content section, select Embed & Sign for both XCFrameworks.
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 andreloadData()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.