Skip to content

Implementing Storyteller Delegate Callbacks#

A StorytellerDelegate has methods for managing Storyteller events. It is used as a global object for handling all events when opening a story/clips player. Please see the dedicated StorytellerListViewDelegate below for handling events related to rows and grids. The StorytellerDelegate inherits from the StorytellerModule protocol, which you can refer to here.

StorytellerDelegate#

You can implement these optional methods when conforming to the StorytellerDelegate protocol:

onUserActivityOccurred#

The onUserActivityOccurred(type: StorytellerUserActivity.EventType, data: StorytellerUserActivityData) method is called when an analytics event is triggered within the SDK. This allows the integrating app to observe and potentially forward these events to their own analytics systems. See the Analytics page for details on event types and data.

getAd#

The getAd(for adRequestInfo: StorytellerAdRequestInfo) async throws -> StorytellerAd method is called when the tenant is configured in the CMS to request full-screen ads from the integrating app. The app should fetch ad data asynchronously and return it directly to the SDK, or throw an error if no ad is available. See the Ads page for more details.

getBottomBannerAd#

getBottomBannerAd(for adRequestInfo: StorytellerAdRequestInfo, maxHeight: CGFloat) async throws -> StorytellerAd method is similar to getAd, but is called when the tenant is configured in the CMS to request bottom banner ads (displayed at the bottom of clips) from the integrating app. The maxHeight parameter indicates the maximum allowed height for the banner based on the current layout constraints. See the Ads page for more details.

userNavigatedToApp#

The userNavigatedToApp(url: String) method is called when a user presses an action button on a page which should direct the user to a specific place within the integrating app. More information on In App links Navigating to App. For more information on deep linking, see the dedicated Deep linking page.

configureWebView#

This method allows you to configure the WebView with custom settings or actions when the Storyteller SDK is about to display a WebView. This method is called before displaying WebView on the screen.

It receives a configuration object which is a collection of properties that you use to initialize a web view.

categoryFollowActionTaken#

The method categoryFollowActionTaken(category: StorytellerCategory, isFollowing: Bool) is invoked when a user follows or unfollows a category of clips (the category can represent a player, a team etc.) from within the SDK's UI.

This delegate method receives the following parameters:

  • category - An object representing the clip category
  • isFollowing - A boolean value indicating whether the user is following or unfollowing the specified category

Note: This method is only called when your tenant is setup in App-Managed Following mode.

onPlayerPresented#

The method onPlayerPresented() is invoked when a story or clip player is presented on screen. This can be useful for pausing background audio, videos or animations in your app while the player is visible.

onPlayerDismissed#

The method onPlayerDismissed() is invoked when a story or clip player is dismissed from screen. This can be useful for resuming background audio, videos or animations in your app that were paused when the player was presented.

viewController(for: StorytellerCategory)#

The method viewController(for category: StorytellerCategory) -> UIViewController? is invoked when the user taps on the category icon inside a clip or interactively swipes to the left. You can provide a custom view controller to push to. This method is optional, and if an implementation is not provided, the SDK will push a UIViewController with a Story Row and Clip Grid based on the Category.

This delegate method receives the following parameter:

  • category - An object representing the clip category

Example implementation of StorytellerDelegate#

Here's an example of a class that implements all delegate methods:

class DelegateObject : StorytellerDelegate {

    func onUserActivityOccurred(type: StorytellerUserActivity.EventType, data: StorytellerUserActivityData) {
        // Actions to handle analytics data
        print("Event: \(type) \(data)")
    }

    func getAd(for adRequestInfo: StorytellerAdRequestInfo) async throws -> StorytellerAd {
        // Request ad from your ads provider then return it to the SDK
        print("Ad Request: \(adRequestInfo)")
        // Example: return yourStorytellerAd, or throw an error
    }

    func getBottomBannerAd(for adRequestInfo: StorytellerAdRequestInfo, maxHeight: CGFloat) async throws -> StorytellerAd {
        // Request bottom banner ad from your ads provider then return it to the SDK
        print("Bottom Banner Ad Request: \(adRequestInfo)")
        // Example: return yourStorytellerAd, or throw an error
    }

    func userNavigatedToApp(url: String) {
        // The user navigated to app by pressing action button on a page which should redirect to a location within the integrating app. The url describes where in the integrating app the user should be directed
    }

    func configureWebView(configuration: WKWebViewConfiguration) {
        // Your custom configuration code goes here
        // The following line shows a change of the backgroundColor of webView to red.
        let contentController = WKUserContentController()
        let scriptSource = "document.body.style.backgroundColor = `red`;"
        let script = WKUserScript(source: scriptSource, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        contentController.addUserScript(script)

        configuration.userContentController = contentController
    }

    func categoryFollowActionTaken(category: StorytellerCategory, isFollowing: Bool) {
        if isFollowing {
            // add the category to list of followed categories
        } else {
            // remove the category from list of followed categories
        }
    }

    func viewController(for category: StorytellerCategory) -> UIViewController? {
        CustomCategoryViewController(for: category)
    }
}

DelegateObject should be assigned to Storyteller's delegate. It should be stored strongly somewhere in your app, as Storyteller holds a weak reference to it.

// Store it strongly somewhere
let delegate = DelegateObject()

Storyteller.shared.delegate = delegate

StorytellerListViewDelegate#

A StorytellerListViewDelegate has methods for managing StorytellerRowView and StorytellerGridView Story and StorytellerClipsRowView and StorytellerClipsGridView Clip events. You can implement these optional methods when implementing to the StorytellerListViewDelegate protocol:

onDataLoadStarted#

The onDataLoadStarted() is called when the network request to load data for all Stories has started

onDataLoadComplete#

The onDataLoadComplete(success: Bool, error: Error?, dataCount: Int) is called when the data loading network request is complete

Property Description
success This confirms whether or not the request was successful
error The HTTP error if the request was not successful
dataCount The number of Stories loaded

onTileTapped#

The onTileTapped(type: StorytellerTileType) is called when a user taps on a tile inside a row or grid. This callback is executed before the player is opened.

Property Description
type A StorytellerTileType enum that contains tile information. Can be either .story(storyId: String, categories: [StorytellerCategoryDetail]) or .clip(clipId: String, collectionId: String, categories: [StorytellerCategoryDetail])

Note: When theme.lists.enablePlayerOpen is set to false, the SDK will not automatically open the player and you should handle your custom tile interaction logic via this callback. For lists in SDK‑owned screens (Storyteller Home, Followable Categories, and Search), the SDK always opens the player when a tile is tapped, regardless of theme.lists.enablePlayerOpen.

Example:

// Assuming `theme.lists.enablePlayerOpen` is set to `false`
func onTileTapped(type: StorytellerTileType) {
    switch type {
    case .story(let storyId, let categories):
        let categoryIds = categories.map(\.id)
        // Handle story tile tap
    case .clip(let clipId, let collectionId, let categories):
        let categoryIds = categories.map(\.id)
        // Handle clip tile tap
    }
}

onPlayerDismissed#

The onPlayerDismissed() is called when any Story has been dismissed

Error Handling#

By using the callback function onDataLoadComplete and the data it provides, you can handle the current state of the StorytellerRowView appropriately in your app.

Note: dataCount is the total number of Stories in the existing StorytellerRowView at any given time

Example:

...
func onDataLoadComplete(success: Bool, error: Error?, dataCount: Int) {
    if success {
        // stories data has been loaded successfully
        // dataCount is the current total number of content, including newly added/removed data
    } else if let newError = error {
        // an error has occurred, use the unwrapped value `newError`
    }
}

Another example:

...
func onDataLoadComplete(success: Bool, error: Error?, dataCount: Int) {
    if let _ = error, dataCount == 0 {
        // content have failed to load with error and there is no data to show
        // you may wish to hide the `StorytellerRowView` instance here
        storytellerRowView.isHidden = true
        // Example: storytellerRowViewHeightConstraint.constant = 0
    }
}

Example implementation of StorytellerListViewDelegate#

class DelegateObject : StorytellerListViewDelegate {

    func onDataLoadStarted() {
        // Action on start of data network requests
    }

    func onDataLoadComplete(success: Bool, error: Error?, dataCount: Int) {
        // Action on completion of data network requests
    }

    func onTileTapped(type: StorytellerTileType) {
        // Action when a tile is tapped
    }

    func onPlayerDismissed() {
        // Action on dismissal of player
    }
}

Once an instance of this has been created, you can then set it to the delegate property of your StorytellerRowView, StorytellerGridView,StorytellerClipsRowView or StorytellerClipsGridView as follows:

let storytellerRow = StorytellerRowView()
let delegate = DelegateObject()

storytellerRow.delegate = delegate
storytellerRow.reloadData()

Make sure reloadData() is called on StorytellerRowView, StorytellerGridView, StorytellerClipsRowView or StorytellerClipsGridView instance after assignment, otherwise the delegate methods onDataLoadStarted() and onDataLoadComplete() will not be called.