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.
- Xcode 15 or later.
- 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.
- In Xcode, open File > Add Packages….
- 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.
- Download XCFrameworks from the iOS Quickstart Guide.
- Add the frameworks to your tvOS target.
- Add Lottie
4.5.2or later, below5.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. - 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 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.