Skip to content

The Storyteller List Views#

Use Storyteller List Views to embed a row or grid of Stories or Clips in your own UIKit or SwiftUI screen. If you have not displayed Storyteller content before, complete the iOS Quickstart Guide first.

Choose the content, layout, and framework independently:

Content and layout UIKit SwiftUI
Stories row StorytellerStoriesRowView StorytellerStoriesRow with StorytellerStoriesListModel
Stories grid StorytellerStoriesGridView StorytellerStoriesGrid with StorytellerStoriesListModel
Clips row StorytellerClipsRowView StorytellerClipsRow with StorytellerClipsListModel
Clips grid StorytellerClipsGridView StorytellerClipsGrid with StorytellerClipsListModel

The StorytellerStoriesListConfiguration and StorytellerClipsListConfiguration values are shared by both frameworks. UIKit views report loading and interaction through StorytellerListViewDelegate; SwiftUI wrappers expose the equivalent events through StorytellerListActionCallback.

UIKit List Types#

The four final UIKit List views have the following class hierarchy (root class is at the top):

 StorytellerListView
 ├── StorytellerRowView
 │   ├── StorytellerStoriesRowView
 │   └── StorytellerClipsRowView
 └── StorytellerGridView
     ├── StorytellerStoriesGridView
     └── StorytellerClipsGridView

Choose the one you need depending on content and UI behavior. Use a Stories or Clips list, then choose between these layouts:

Rows are horizontal scrolling lists, that you create by simply calling:

let storiesRow = StorytellerStoriesRowView()

or

let clipsRow = StorytellerClipsRowView()

For a UIKit table view integration (including configuration and delegate wiring), see the CocoaPods sample in StorytellerStoriesRowTableViewCell.

When using rows, you can either provide explicit height constraints or let the SDK manage the height automatically. When no explicit height constraint is set, the SDK will automatically calculate and adjust the row height, including adjustments for iOS Dynamic Type settings to improve accessibility.

Grids are vertical lists, organized into columns (number of columns can be set on the Theme), and can be scrollable or not. They can be constructed like so:

let storiesGrid = StorytellerStoriesGridView(isScrollable: true) // scrollable
let clipsGrid = StorytellerClipsGridView(isScrollable: false) // non-scrollable

For a full SwiftUI Showcase Home feed that composes rows, grids, and Cards, see how the Showcase app assembles the feed in HomeView, renders items in StorytellerItemView, and uses list wrappers in StoriesListView.

Scrollable vs non-scrollable Grids#

The choice between scrollable and non-scrollable grids applies to both frameworks. In UIKit, non-scrollable grids can be used inside a vertical feed where you might also have rows or other UI elements in the same view. Constrain just the width of the grid and leave its height to be calculated automatically by Auto Layout. Auto-resize can happen whenever the delegate callback onDataLoadComplete is triggered (see more list events).

Note - if you use a non-scrollable grid with many items (hundreds) your app might hang when it loads due to the fact that it is not optimized to handle a huge amount of items. For that you would either set a displayLimit or use a scrollable Grid. When no limit is provided, non-scrollable grids default to rendering at most 30 items.

Scrollable grids on the other hand support recycling and can be used with a large number of items. It has scrolling and even pull-to-refresh builtin. It is recommended to build a "More" screen for example, where the grid is the main view on the screen. You need to constrain both its width and height when integrating it in a view hierarchy.

Interface Builder support#

This section applies only to UIKit. UIKit List views support being created in XIBs and storyboards. Type one of the four custom final classes in the Class field in the Identity Inspector of the desired view.

Remember grids default to non-scrolling. We added an IBInspectable property so that you can enable scrolling, but it seems Xcode doesn't detect it when using a custom view class from a 3rd party framework. The workaround is to select the grid, open the Identity Inspector, and under User Defined Runtime Attributes add a key path with the value isScrollable of type Boolean and ensure the checkbox next to it is selected.

For examples on how to construct the lists in various ways, refer to the Showcase implementations of rows/grids in StoriesListView and list callbacks in StorytellerItemView.listAction.

Further configuration#

The configuration values in this section are shared by UIKit and SwiftUI. UIKit views receive a configuration through configure(with:); SwiftUI wrappers receive a model initialized with the same configuration type.

UIKit views expose a weak delegate property for list events. Keep that delegate strongly referenced in your app. SwiftUI wrappers instead receive a StorytellerListActionCallback through their action parameter.

For stories you pass in a StorytellerStoriesListConfiguration, containing this specific Stories parameter:

  • categories - a list of strings to specify the content from what categories the list will be displaying. Note, this is settable only on Story views.

For clips you pass in a StorytellerClipsListConfiguration, containing this specific Clips parameter:

  • collectionId - can be set only on Clip views, and it represents a string identifying the clip collection to be displayed.

StorytellerClipsListConfiguration configures Clip tiles in a row or grid. It is different from StorytellerClipCollectionConfiguration, which configures an embedded or programmatically presented Clip Player, including its destination and Ad placements. See Embedded Clips for the Player configuration.

The next parameters are common for both the configuration objects:

  • cellType - choose between round and rectangular cells. The latter have an aspect ratio of 2/3.
  • theme - this property defines the overall appearance of the list, as well as the player that is presented when tapping on an item.
  • uiStyle - override this to make the list display in dark or light mode. The default is auto, which is in sync with the native iOS user interface style.
  • displayLimit - limit the number of items shown in the list. nil, 0 or negative value means no limit for rows and scrollable grids, non-scrollable grids default to 30 items when no limit is provided.
  • visibleTiles - limits the number of visible items in the row list. When set to a non-nil value, the row adjusts its height dynamically. Therefore, avoid setting a fixed row height when configuring this property. This configuration will be automatically adjusted based on the iOS Dynamic Type settings - when users change their text size, the number of visible tiles may be reduced/increased to maintain readability and proper spacing. The configured value represents the target number of tiles at the default system text size. The default value is nil.
  • context - optional context data that will be included in analytics callbacks for attribution. This allows you to track which sources drive engagement with your content. When configured, context will be included in all analytics events for interactions with the list content. See Analytics for more details.

After configuring a UIKit view, call reloadData() to perform its initial fetch. Call it again after changing the configuration or whenever you need to refresh its content. SwiftUI wrappers perform their initial load from the supplied model; call reloadData() on the model when you need a later refresh without changing its configuration.

Configure Example#

Create a UIKit view, configure it, then call reloadData() to perform the initial fetch. Call it again whenever you need to refresh the content. Assign a strongly retained StorytellerListViewDelegate before loading when you need lifecycle or interaction callbacks.

import StorytellerSDK

let storytellerStoriesRow = StorytellerStoriesRowView()
let storytellerClipsRow = StorytellerClipsRowView()

// Stories configuration with context
storytellerStoriesRow.configure(with: StorytellerStoriesListConfiguration(
    categories: ["sports", "entertainment"],
    context: [
        "source": "home-screen-stories",
        "campaign": "summer-league"
    ]
))
storytellerStoriesRow.reloadData()

// Clips configuration with context
storytellerClipsRow.configure(with: StorytellerClipsListConfiguration(
    collectionId: "trending-clips",
    context: [
        "source": "home-screen-clips",
        "campaign": "summer-league"
    ]
))
storytellerClipsRow.reloadData()

Construct models from the shared configuration types, retain them as view state, and supply a StorytellerListActionCallback to each wrapper. For a real-world setup, see the Showcase wrappers in StoriesListView.

import StorytellerSDK
import SwiftUI

@available(iOS 15.0, *)
struct StorytellerListsView: View {
    @State private var storiesModel = StorytellerStoriesListModel(
        configuration: StorytellerStoriesListConfiguration(
            categories: ["sports"],
            context: ["location": "stories-grid"]
        )
    )
    @State private var clipsModel = StorytellerClipsListModel(
        configuration: StorytellerClipsListConfiguration(
            collectionId: "top-plays",
            context: ["location": "clips-grid"]
        )
    )

    private let handleListAction: StorytellerListActionCallback = { action in
        switch action {
        case .onDataLoadStarted:
            print("Storyteller list started loading")
        case .onDataLoadComplete(let success, let error, let dataCount):
            print("Loaded \(dataCount) items; success: \(success); error: \(error?.localizedDescription ?? "none")")
        case .onTileTapped(let type):
            print("Tapped Storyteller tile: \(type)")
        case .onPlayerDismissed:
            print("Storyteller Player dismissed")
        @unknown default:
            break
        }
    }

    var body: some View {
        VStack {
            StorytellerStoriesGrid(
                isScrollable: true,
                model: storiesModel,
                action: handleListAction
            )
            .frame(height: 300)

            StorytellerClipsGrid(
                isScrollable: true,
                model: clipsModel,
                action: handleListAction
            )
            .frame(height: 300)
        }
        .refreshable {
            storiesModel.reloadData()
            clipsModel.reloadData()
        }
    }
}

Use the same models with StorytellerStoriesRow or StorytellerClipsRow when you need horizontal rows. SwiftUI reloads a component when its model changes; call the model's reloadData() method to reload without changing its configuration.

Continue Your Integration#