The Storyteller List views#
We have 4 flavours of final List views that you can use, that 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 two criteria, content and UI behaviour. When it comes to content, you can choose between a Stories or a Clips version of the list. As far as UI is concerned, you have 2 options:
Rows are horizontal scrolling lists, that you create by simply calling:
let storiesRow = StorytellerStoriesRowView()
or
let clipsRow = StorytellerClipsRowView()
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
Scrollable vs non-scrollable Grids#
Non-scrollable grids can be used when used for example inside a vertical feed where you might also have other items, like rows or other UI elements of your own in the same view. In that case, you should constrain just the width of the grid and leave its height to be calculated automatically by Autolayout. Auto-resize can happen whenever the delegate callback onDataLoadCompleted 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
displayLimitor 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#
Our lists support being created in xibs/storyboards. All you have to do is type in 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 our Showcase App.
Further configuration#
After creating a list view, you can setup delegate through field:
delegate- set it to respond to various list events.
Further configuration can be executed via the configure(with:) method:
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.
The next parameters are common for both the configuration objects:
cellType- choose between round and rectangular cells. The latter have an aspect ration 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,0or negative value means no limit for rows and scrollable grids, non-scrollable grids default to 30 items when no limit is provided.offset- applies a zero-based index offset before rendering items, allowing the list to start at a specific index.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 isnil.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,contextwill be included in all analytics events for interactions with the list content. See Analytics for more details.
After the view is configured and every time you perform a change, you can call reloadData() on it at which point it will fetch and display new items with the new configurations.
Configure Example#
// Stories configuration with context
storytellerStoriesRow.configure(with: StorytellerStoriesListConfiguration(
categories: ["sports", "entertainment"],
context: [
"source": "home-screen-stories",
"campaign": "summer-league"
]
))
// Clips configuration with context
storytellerClipsRow.configure(with: StorytellerClipsListConfiguration(
collectionId: "trending-clips",
context: [
"source": "home-screen-clips",
"campaign": "summer-league"
]
))
SwiftUI#
To implement list views in SwiftUI special wrappers are exposed.
For stories:
StorytellerStoriesRowStorytellerStoriesGrid
For clips:
StorytellerClipsRowStorytellerClipsGrid
They are configured by the same configuration objects described above. Configuration is provided on the init of the list view. Examples below on how to construct the lists in various ways, for more, refer to our Showcase App.
struct SwiftUIView: View {
class SwiftUIModel: ObservableObject {
@Published var storiesRowModel: StorytellerStoriesListModel
@Published var storiesGridModel: StorytellerStoriesListModel
@Published var clipsRowModel: StorytellerClipsListModel
@Published var clipsGridModel: StorytellerClipsListModel
init(storiesRowModel: StorytellerStoriesListModel, storiesGridModel: StorytellerStoriesListModel, clipsRowModel: StorytellerClipsListModel, clipsGridModel: StorytellerClipsListModel) {
self.storiesRowModel = storiesRowModel
self.storiesGridModel = storiesGridModel
self.clipsRowModel = clipsRowModel
self.clipsGridModel = clipsGridModel
}
func reloadData() {
storiesRowModel.reloadData()
storiesGridModel.reloadData()
clipsRowModel.reloadData()
clipsGridModel.reloadData()
}
}
@StateObject var model: SwiftUIModel
var storytellerListAction: StorytellerListActionCallback = { action in
switch action {
case .onDataLoadComplete(let success, let error, let dataCount):
print("[SwiftUI] onDataLoadComplete success: \(success) error: \(error) dataCount: \(dataCount)")
case .onDataLoadStarted:
print("[SwiftUI] onDataLoadStarted")
case .onPlayerDismissed:
print("[SwiftUI] onPlayerDismissed")
}
}
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text("SwiftUI Stories RowView")
.padding(.leading, 8)
StorytellerStoriesRow(model: model.storiesRowModel, action: storytellerListAction)
.frame(height: 240)
Text("SwiftUI Stories GridView")
.padding(.leading, 8)
StorytellerStoriesGrid(isScrollable: false, model: model.storiesGridModel, action: storytellerListAction)
Text("SwiftUI Clips RowView")
.padding(.leading, 8)
StorytellerClipsRow(model: model.clipsRowModel, action: storytellerListAction)
.frame(height: 240)
Text("SwiftUI Clips GridView")
.padding(.leading, 8)
StorytellerClipsGrid(isScrollable: false, model: model.clipsGridModel, action: storytellerListAction)
}
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)
}
.refreshable {
model.reloadData()
}
.padding(.top, 16)
}
}
SwiftUI reloads its components whenever the model provided to it changes. If you need to reload data without changing configuration in model then you should call reloadData on the model object provided.
SwiftUI Scrollable Grids#
In SwiftUI you can create scrollable grids by setting isScrollable to true in the initializer of View component.
For stories:
StorytellerStoriesGrid(isScrollable: true, model: model.storiesGridModel, action: storytellerInstanceDelegateHandler)
For clips:
StorytellerClipsGrid(isScrollable: true, model: model.clipsGridModel, action: storytellerInstanceDelegateHandler)
Configuration for SwiftUI views is handled via their respective models (StorytellerStoriesListModel, StorytellerClipsListModel), which contain properties corresponding to the UIKit configuration options described above (e.g., categories, collectionId, theme, uiStyle, cellType, displayLimit, visibleTiles, context).