Skip to content

Migrating to version 11#

This guide helps you migrate from Storyteller SDK version 10.x.x to 11.0.x. Version 11.0.x introduces several breaking changes that improve the SDK's architecture and consistency. Version 11 focuses on three major architectural improvements:

  • Shared Instance Pattern - Static methods replaced with Storyteller.shared
  • Consistent Naming - Public types now use Storyteller prefix
  • Modern Swift Concurrency - Callback-based APIs replaced with async/await

Storyteller shared instance#

The Storyteller class has moved from using static methods to a shared instance pattern. All API calls must now use Storyteller.shared instead of calling static methods directly on the class.

Usage example#

Before (10.x.x):

Storyteller.delegate = myDelegate

After (11.x.x):

final class DelegateObject: StorytellerDelegate {}
let myDelegate = DelegateObject()
Storyteller.shared.delegate = myDelegate

See the shared instance pattern in the Showcase app where Storyteller.shared.modules, Storyteller.shared.theme, and Storyteller.shared.delegate are configured in AppDelegate.setupStoryteller.

Storyteller Prefix for Public Types#

All public types now start with the Storyteller prefix for better namespace consistency and to avoid naming conflicts with your app code.

Type Renames#

Old Name New Name
UserInput StorytellerUserInput
ClipCollectionConfiguration StorytellerClipCollectionConfiguration
Placement StorytellerPlacement
Category StorytellerCategory
CategoryDetail StorytellerCategoryDetail
CurrentCategoryData StorytellerCurrentCategoryData
UserActivity StorytellerUserActivity
UserActivityData StorytellerUserActivityData
CodableIgnored StorytellerCodableIgnored
Alignment StorytellerAlignment
FontProvider StorytellerFontProvider
TextCasing StorytellerTextCasing
PlayerIcons StorytellerPlayerIcons
InstructionIcons StorytellerInstructionIcons

Async/Await functions#

All callback-based APIs have been replaced with modern Swift async/await patterns. The following Storyteller.shared methods are now async functions:

  • initialize(apiKey:userInput:eventTrackingOptions:)
  • dismissPlayer(animated:dismissReason:)
  • openDeepLink(url:)
  • openStory(id:openReason:)
  • openStory(externalId:openReason:)
  • openPage(id:openReason:)
  • openCategory(category:openReason:)
  • openCollection(configuration:openReason:)
  • openClipByExternalId(collectionId:externalId:openReason:)
  • openSheet(id:)
  • getStoriesCount(for:)
  • getClipsCount(for:)
  • openSearch()

Migration Examples#

Before (10.x.x):

Storyteller.initialize(
    apiKey: "your-api-key",
    onComplete: {
        print("SDK initialized successfully")
    },
    onError: { error in
        print("Initialization failed: \(error)")
    }
)

After (11.x.x):

Task {
    do {
        try await Storyteller.shared.initialize(apiKey: "your-api-key")
        print("SDK initialized successfully")
    } catch {
        print("Initialization failed: \(error)")
    }
}

See the Showcase initialization flow using async/await in StorytellerService.setup.

Additional Breaking Changes#

Event Tracking Options#

eventTrackingOptions can now only be set during SDK initialization:

Before (10.x.x):

// Initialize SDK
Storyteller.initialize(
    apiKey: "your-api-key",
    onComplete: {
        print("SDK initialized successfully")
    },
    onError: { error in
        print("Initialization failed: \(error)")
    }
)

// Later in the code, modify tracking options
Storyteller.eventTrackingOptions = StorytellerEventTrackingOptions(
    enablePersonalization: true,
    enableStorytellerTracking: true,
    enableUserActivityTracking: true,
    enableAdTracking: true,
    enableFullVideoAnalytics: true,
    enableRemoteViewingStore: true,
    disabledFunctionalFeatures: []
)

After (11.x.x):

// Set tracking options during initialization
let trackingOptions = StorytellerEventTrackingOptions(
    enablePersonalization: true,
    enableStorytellerTracking: true,
    enableUserActivityTracking: true,
    enableAdTracking: true,
    enableFullVideoAnalytics: true,
    enableRemoteViewingStore: true,
    disabledFunctionalFeatures: []
)

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

Task {
    try await Storyteller.shared.initialize(
        apiKey: "your-api-key",
        userInput: userInput,
        eventTrackingOptions: trackingOptions
    )
}

See how the Showcase app builds StorytellerEventTrackingOptions and passes them during initialization in StorytellerService.setup.

To change tracking options after initialization, you must reinitialize the SDK. See Privacy and Tracking for more information.

SwiftUI Grids#

The isScrollable parameter no longer has a default value and must be explicitly provided:

Before (10.x.x):

// isScrollable defaulted to false
StorytellerStoriesGrid(model: storiesModel)
StorytellerClipsGrid(model: clipsModel)

After (11.x.x):

// isScrollable must be explicitly provided
let storiesModel = StorytellerStoriesListModel(
    configuration: StorytellerStoriesListConfiguration(categories: ["category-id"])
)
let clipsModel = StorytellerClipsListModel(
    configuration: StorytellerClipsListConfiguration(collectionId: "collection-id")
)
StorytellerStoriesGrid(isScrollable: false, model: storiesModel)
StorytellerClipsGrid(isScrollable: false, model: clipsModel)

See the Showcase SwiftUI grid usage in StoriesListView.

StorytellerListViewDelegate#

The onTileTapped method now provides richer context via the StorytellerTileType enum:

Before (10.x.x):

extension MyViewController: StorytellerListViewDelegate {
    func onTileTapped(id: String) {
        print("Tapped tile with ID: \(id)")
    }
}

After (11.x.x):

final class MyViewController: UIViewController, StorytellerListViewDelegate {
    nonisolated func onTileTapped(type: StorytellerTileType) {
        switch type {
        case .clip(let clipId, let collectionId, let categories):
            print("Tapped clip: \(clipId) in collection: \(collectionId), categories: \(categories)")
        case .story(let storyId, let categories):
            print("Tapped story: \(storyId), categories: \(categories)")
        @unknown default:
            break
        }
    }
}

See the Showcase onTileTapped flow (including categories) in StorytellerItemView.listAction.

From 11.8.0, onTileTapped(data:) replaces onTileTapped(type:). See Tile tap data in 11.8.0.

Other breaking changes#

The following theme properties have been removed and are now configured in the CMS:

  • tiles.title.show - configured in CMS
  • engagement.poll.showVoteCount - configured in CMS

Tile tap data in 11.8.0#

Version 11.8.0 reports tile taps with StorytellerTileTapData, which contains the tile's type, title, 1-based tileIndex and metadata. UIKit receives it through the list delegate and SwiftUI through the list action.

UIKit list delegate#

StorytellerListViewDelegate.onTileTapped(data:) replaces onTileTapped(type:) and the StorytellerListView.onTileTapped closure. Both are deprecated and will be removed in a future release. Until then they are still called for every tap, so move your handling to the new method instead of implementing both.

Before (11.7.x):

storiesRow.delegate = self
storiesRow.onTileTapped = { data in
    print("Tapped tile \(data.tileIndex): \(data.title ?? "Untitled")")
}

func onTileTapped(type: StorytellerTileType) {
    // Handle the tap
}

After (11.8.0):

final class StoriesRowDelegate: StorytellerListViewDelegate {
    func onTileTapped(data: StorytellerTileTapData) {
        print("Tapped tile \(data.tileIndex): \(data.title ?? "Untitled")")
    }
}

SwiftUI list action#

StorytellerListAction.onTileTapped(type:) is now .onTileTapped(data:). Update action handlers that read the tapped tile's StorytellerTileType or use the type: label; read the type from data.type instead. The row and grid initializers that take an onTileTapped closure are deprecated; read the same data from the action instead.

Before (11.7.x):

case .onTileTapped(let type):
    switch type {
    case .story(let storyId, _):
        print("Tapped story \(storyId)")
    case .clip(let clipId, _, _):
        print("Tapped clip \(clipId)")
    @unknown default:
        break
    }

After (11.8.0):

let handleListAction: StorytellerListActionCallback = { action in
    switch action {
    case .onTileTapped(let data):
        switch data.type {
        case .story(let storyId, _):
            print("Tapped story \(storyId) at position \(data.tileIndex)")
        case .clip(let clipId, _, _):
            print("Tapped clip \(clipId) at position \(data.tileIndex)")
        @unknown default:
            break
        }
    default:
        break
    }
}

Need Help#

If you encounter any issues during the migration:

  1. Check the Changelog for a detailed version history
  2. Don't hesitate to reach out if you continue to face difficulties