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
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.
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.
eventTrackingOptions can now only be set during SDK initialization:
Before (10.x.x):
// Initialize SDKStoryteller.initialize(apiKey:"your-api-key",onComplete:{print("SDK initialized successfully")},onError:{errorinprint("Initialization failed: \(error)")})// Later in the code, modify tracking optionsStoryteller.eventTrackingOptions=StorytellerEventTrackingOptions(enablePersonalization:true,enableStorytellerTracking:true,enableUserActivityTracking:true,enableAdTracking:true,enableFullVideoAnalytics:true,enableRemoteViewingStore:true,disabledFunctionalFeatures:[])
After (11.x.x):
// Set tracking options during initializationlettrackingOptions=StorytellerEventTrackingOptions(enablePersonalization:true,enableStorytellerTracking:true,enableUserActivityTracking:true,enableAdTracking:true,enableFullVideoAnalytics:true,enableRemoteViewingStore:true,disabledFunctionalFeatures:[])letuserInput=StorytellerUserInput(externalId:"user-id")Task{tryawaitStoryteller.shared.initialize(apiKey:"your-api-key",userInput:userInput,eventTrackingOptions:trackingOptions)}
The isScrollable parameter no longer has a default value and must be explicitly provided:
Before (10.x.x):
// isScrollable defaulted to falseStorytellerStoriesGrid(model:storiesModel)StorytellerClipsGrid(model:clipsModel)
After (11.x.x):
// isScrollable must be explicitly providedletstoriesModel=StorytellerStoriesListModel(configuration:StorytellerStoriesListConfiguration(categories:["category-id"]))letclipsModel=StorytellerClipsListModel(configuration:StorytellerClipsListConfiguration(collectionId:"collection-id"))StorytellerStoriesGrid(isScrollable:false,model:storiesModel)StorytellerClipsGrid(isScrollable:false,model:clipsModel)
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.
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=selfstoriesRow.onTileTapped={datainprint("Tapped tile \(data.tileIndex): \(data.title??"Untitled")")}funconTileTapped(type:StorytellerTileType){// Handle the tap}
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(lettype):switchtype{case.story(letstoryId,_):print("Tapped story \(storyId)")case.clip(letclipId,_,_):print("Tapped clip \(clipId)")@unknowndefault:break}
After (11.8.0):
lethandleListAction:StorytellerListActionCallback={actioninswitchaction{case.onTileTapped(letdata):switchdata.type{case.story(letstoryId,_):print("Tapped story \(storyId) at position \(data.tileIndex)")case.clip(letclipId,_,_):print("Tapped clip \(clipId) at position \(data.tileIndex)")@unknowndefault:break}default:break}}
Check the Changelog for a detailed version history
Don't hesitate to reach out if you continue to face difficulties
{"slug": "migration-guide-v11", "page_title": "Migrate from Version 10 to 11", "page_url": "MigrationGuideV11/", "canonical_url": "/ios/MigrationGuideV11/", "markdown": "# Migrating to version 11\n\nThis 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:\n\n- **Shared Instance Pattern** - Static methods replaced with `Storyteller.shared`\n- **Consistent Naming** - Public types now use `Storyteller` prefix\n- **Modern Swift Concurrency** - Callback-based APIs replaced with async/await\n\n## Storyteller shared instance\n\nThe `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.\n\n### Usage example\n\n**Before (10.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-01-before target=historical reason=\"SDK 10 static delegate API\" -->\n\n```swift\nStoryteller.delegate = myDelegate\n```\n\n**After (11.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-01-after target=sdk-ios context=statements -->\n\n```swift\nfinal class DelegateObject: StorytellerDelegate {}\nlet myDelegate = DelegateObject()\nStoryteller.shared.delegate = myDelegate\n```\n\nSee the shared instance pattern in the Showcase app where `Storyteller.shared.modules`, `Storyteller.shared.theme`, and `Storyteller.shared.delegate` are configured in [`AppDelegate.setupStoryteller`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.8.0/main/ShowcaseApp/ShowcaseApp.swift#L95).\n\n## Storyteller Prefix for Public Types\n\nAll public types now start with the `Storyteller` prefix for better namespace consistency and to avoid naming conflicts with your app code.\n\n### Type Renames\n\n| Old Name | New Name |\n|----------|----------|\n| `UserInput` | `StorytellerUserInput` |\n| `ClipCollectionConfiguration` | `StorytellerClipCollectionConfiguration` |\n| `Placement` | `StorytellerPlacement` |\n| `Category` | `StorytellerCategory` |\n| `CategoryDetail` | `StorytellerCategoryDetail` |\n| `CurrentCategoryData` | `StorytellerCurrentCategoryData` |\n| `UserActivity` | `StorytellerUserActivity` |\n| `UserActivityData` | `StorytellerUserActivityData` |\n| `CodableIgnored` | `StorytellerCodableIgnored` |\n| `Alignment` | `StorytellerAlignment` |\n| `FontProvider` | `StorytellerFontProvider` |\n| `TextCasing` | `StorytellerTextCasing` |\n| `PlayerIcons` | `StorytellerPlayerIcons` |\n| `InstructionIcons` | `StorytellerInstructionIcons` |\n\n## Async/Await functions\n\nAll callback-based APIs have been replaced with modern Swift async/await patterns. The following `Storyteller.shared` methods are now async functions:\n\n- `initialize(apiKey:userInput:eventTrackingOptions:)`\n- `dismissPlayer(animated:dismissReason:)`\n- `openDeepLink(url:)`\n- `openStory(id:openReason:)`\n- `openStory(externalId:openReason:)`\n- `openPage(id:openReason:)`\n- `openCategory(category:openReason:)`\n- `openCollection(configuration:openReason:)`\n- `openClipByExternalId(collectionId:externalId:openReason:)`\n- `openSheet(id:)`\n- `getStoriesCount(for:)`\n- `getClipsCount(for:)`\n- `openSearch()`\n\n### Migration Examples\n\n**Before (10.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-02-before target=historical reason=\"SDK 10 callback initialization API\" -->\n\n```swift\nStoryteller.initialize(\n apiKey: \"your-api-key\",\n onComplete: {\n print(\"SDK initialized successfully\")\n },\n onError: { error in\n print(\"Initialization failed: \\(error)\")\n }\n)\n```\n\n**After (11.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-02-after target=sdk-ios context=statements -->\n\n```swift\nTask {\n do {\n try await Storyteller.shared.initialize(apiKey: \"your-api-key\")\n print(\"SDK initialized successfully\")\n } catch {\n print(\"Initialization failed: \\(error)\")\n }\n}\n```\n\nSee the Showcase initialization flow using async/await in [`StorytellerService.setup`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.8.0/main/ShowcaseApp/Storyteller/StorytellerService.swift#L36).\n\n## Additional Breaking Changes\n\n### Event Tracking Options\n\n`eventTrackingOptions` can now only be set during SDK initialization:\n\n**Before (10.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-03 target=historical reason=\"SDK 10 mutable tracking options API\" -->\n\n```swift\n// Initialize SDK\nStoryteller.initialize(\n apiKey: \"your-api-key\",\n onComplete: {\n print(\"SDK initialized successfully\")\n },\n onError: { error in\n print(\"Initialization failed: \\(error)\")\n }\n)\n\n// Later in the code, modify tracking options\nStoryteller.eventTrackingOptions = StorytellerEventTrackingOptions(\n enablePersonalization: true,\n enableStorytellerTracking: true,\n enableUserActivityTracking: true,\n enableAdTracking: true,\n enableFullVideoAnalytics: true,\n enableRemoteViewingStore: true,\n disabledFunctionalFeatures: []\n)\n```\n\n**After (11.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-04 target=sdk-ios context=statements -->\n\n```swift\n// Set tracking options during initialization\nlet trackingOptions = StorytellerEventTrackingOptions(\n enablePersonalization: true,\n enableStorytellerTracking: true,\n enableUserActivityTracking: true,\n enableAdTracking: true,\n enableFullVideoAnalytics: true,\n enableRemoteViewingStore: true,\n disabledFunctionalFeatures: []\n)\n\nlet userInput = StorytellerUserInput(externalId: \"user-id\")\n\nTask {\n try await Storyteller.shared.initialize(\n apiKey: \"your-api-key\",\n userInput: userInput,\n eventTrackingOptions: trackingOptions\n )\n}\n```\n\nSee how the Showcase app builds [`StorytellerEventTrackingOptions`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.8.0/main/ShowcaseApp/Storyteller/StorytellerService.swift#L37) and passes them during initialization in [`StorytellerService.setup`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.8.0/main/ShowcaseApp/Storyteller/StorytellerService.swift#L36).\n\nTo change tracking options after initialization, you must reinitialize the SDK. See [Privacy and Tracking](PrivacyAndTracking.md) for more information.\n\n### SwiftUI Grids\n\nThe `isScrollable` parameter no longer has a default value and must be explicitly provided:\n\n**Before (10.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-05-before target=historical reason=\"SDK 10 SwiftUI grid initializer\" -->\n\n```swift\n// isScrollable defaulted to false\nStorytellerStoriesGrid(model: storiesModel)\nStorytellerClipsGrid(model: clipsModel)\n```\n\n**After (11.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-05-after target=sdk-ios context=statements -->\n\n```swift\n// isScrollable must be explicitly provided\nlet storiesModel = StorytellerStoriesListModel(\n configuration: StorytellerStoriesListConfiguration(categories: [\"category-id\"])\n)\nlet clipsModel = StorytellerClipsListModel(\n configuration: StorytellerClipsListConfiguration(collectionId: \"collection-id\")\n)\nStorytellerStoriesGrid(isScrollable: false, model: storiesModel)\nStorytellerClipsGrid(isScrollable: false, model: clipsModel)\n```\n\nSee the Showcase SwiftUI grid usage in [`StoriesListView`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.8.0/main/ShowcaseApp/Views/Home/Components/StorytellerLists.swift#L11).\n\n### StorytellerListViewDelegate\n\nThe `onTileTapped` method now provides richer context via the `StorytellerTileType` enum:\n\n**Before (10.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-06-before target=historical reason=\"SDK 10 tile callback signature\" -->\n\n```swift\nextension MyViewController: StorytellerListViewDelegate {\n func onTileTapped(id: String) {\n print(\"Tapped tile with ID: \\(id)\")\n }\n}\n```\n\n**After (11.x.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-06-after target=sdk-ios context=declarations -->\n\n```swift\nfinal class MyViewController: UIViewController, StorytellerListViewDelegate {\n nonisolated func onTileTapped(type: StorytellerTileType) {\n switch type {\n case .clip(let clipId, let collectionId, let categories):\n print(\"Tapped clip: \\(clipId) in collection: \\(collectionId), categories: \\(categories)\")\n case .story(let storyId, let categories):\n print(\"Tapped story: \\(storyId), categories: \\(categories)\")\n @unknown default:\n break\n }\n }\n}\n```\n\nSee the Showcase [`onTileTapped`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.8.0/main/ShowcaseApp/Views/Home/Components/StorytellerItemView.swift#L65) flow (including categories) in [`StorytellerItemView.listAction`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.8.0/main/ShowcaseApp/Views/Home/Components/StorytellerItemView.swift#L58).\n\nFrom 11.8.0, `onTileTapped(data:)` replaces `onTileTapped(type:)`. See [Tile tap data in 11.8.0](#tile-tap-data-11-8-0).\n\n### Other breaking changes\n\nThe following theme properties have been removed and are now configured in the CMS:\n\n- `tiles.title.show` - configured in CMS\n- `engagement.poll.showVoteCount` - configured in CMS\n\n## Tile tap data in 11.8.0 {#tile-tap-data-11-8-0}\n\nVersion 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.\n\n### UIKit list delegate\n\n`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.\n\n**Before (11.7.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-08-before target=historical reason=\"SDK 11.7 tile tap closure and delegate method\" -->\n\n```swift\nstoriesRow.delegate = self\nstoriesRow.onTileTapped = { data in\n print(\"Tapped tile \\(data.tileIndex): \\(data.title ?? \"Untitled\")\")\n}\n\nfunc onTileTapped(type: StorytellerTileType) {\n // Handle the tap\n}\n```\n\n**After (11.8.0):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-08-after target=sdk-ios context=declarations -->\n\n```swift\nfinal class StoriesRowDelegate: StorytellerListViewDelegate {\n func onTileTapped(data: StorytellerTileTapData) {\n print(\"Tapped tile \\(data.tileIndex): \\(data.title ?? \"Untitled\")\")\n }\n}\n```\n\n### SwiftUI list action\n\n`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.\n\n**Before (11.7.x):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-09-before target=historical reason=\"SDK 11.7 SwiftUI tile tap action\" -->\n\n```swift\ncase .onTileTapped(let type):\n switch type {\n case .story(let storyId, _):\n print(\"Tapped story \\(storyId)\")\n case .clip(let clipId, _, _):\n print(\"Tapped clip \\(clipId)\")\n @unknown default:\n break\n }\n```\n\n**After (11.8.0):**\n\n<!-- storyteller-swift-example: id=migrationguidev11-09-after target=sdk-ios context=statements -->\n\n```swift\nlet handleListAction: StorytellerListActionCallback = { action in\n switch action {\n case .onTileTapped(let data):\n switch data.type {\n case .story(let storyId, _):\n print(\"Tapped story \\(storyId) at position \\(data.tileIndex)\")\n case .clip(let clipId, _, _):\n print(\"Tapped clip \\(clipId) at position \\(data.tileIndex)\")\n @unknown default:\n break\n }\n default:\n break\n }\n}\n```\n\n## Need Help\n\nIf you encounter any issues during the migration:\n\n1. Check the [Changelog](Changelog.md) for a detailed version history\n1. Don't hesitate to reach out if you continue to face difficulties\n", "copy_markdown_include_header": false, "base_path": "", "ai_dir": "ai", "missing_payload_behavior": "empty"}