Storyteller Cards#
Storyteller Cards are flexible, themeable components designed to promote content or direct users to key sections within your app. They can display a background image or video, along with an optional title, subtitle, and button. Tapping on a Card can trigger various actions, such as opening a specific Story, a Story Category, a Clip, a Clip Collection or any other action defined in the CMS. The server or personalization engine can choose which Cards to return for a given user.
Usage#
You can integrate Storyteller Cards into your app using either SwiftUI or UIKit.
SwiftUI#
For SwiftUI, use the StorytellerCard view component.
- Configuration: Create a
StorytellerCardConfigurationobject, specifying thecollectionIdfor the Card collection you want to display. You can also provide optionalcontextdata for analytics attribution. When configured,contextwill be included in all analytics events when users interact with the Card. See Analytics for more details. - Model: Initialize a
StorytellerCardModel(which conforms toObservableObject) with the configuration. - View: Create the
StorytellerCardview, passing in theStorytellerCardModelinstance. - Actions (Optional): Provide an optional callback closure to the
StorytellerCardinitializer to handle events likeonDataLoadComplete. This allows you to react to data loading success or failure (e.g., by hiding the component).
import SwiftUI
import StorytellerSDK
struct SwiftUIView: View {
@StateObject private var cardModel = StorytellerCardModel(
configuration: StorytellerCardConfiguration(
collectionId: "card-collection-id",
context: ["source": "hero-banner"]
)
)
var body: some View {
VStack {
Text("Storyteller Card Section")
StorytellerCard(model: cardModel) { action in
switch action {
case .onDataLoadComplete(let result):
switch result {
case .success:
print("Card data loaded successfully!")
case .failure(let error):
print("Card data failed to load: \(error.localizedDescription)")
}
}
}
Button("Reload Card") {
cardModel.reload()
}
}
.padding()
}
}
UIKit#
For UIKit, use the StorytellerCardView class, which subclasses UIView.
- Configuration: Create a
StorytellerCardConfigurationwith the desiredcollectionId. You can also provide optionalcontextdata for analytics. When configured,contextwill be included in all analytics events when users interact with the Card. See Analytics for more details. - View Initialization: Instantiate
StorytellerCardViewusing the configuration. - Delegate (Optional): Assign an object conforming to
StorytellerCardViewDelegateto the view'sdelegateproperty to receive callbacks likeonDataLoadComplete. - Add to View Hierarchy: Add the
StorytellerCardViewinstance as a subview.
class CardView: UIView, StorytellerCardViewDelegate {
private var storytellerCardView: StorytellerCardView?
// ...
func configure(with collectionId: String, delegate: StorytellerCardViewDelegate?) {
let configuration = StorytellerCardConfiguration(
collectionId: collectionId,
context: [
"source": "hero-banner",
]
)
let cardView = StorytellerCardView(configuration: configuration)
cardView.delegate = delegate
addSubview(cardView)
// Add constraints
self.storytellerCardView = cardView
}
func reloadCard() {
storytellerCardView?.reload()
}
}
We also provide cells to include inside collection and table views - StorytellerCardCollectionViewCell and StorytellerCardTableViewCell. They also follow the above pattern to setup.
Reloading#
Both StorytellerCardModel (for SwiftUI) and the UIKit flavours provide a reload() method. Call this method to manually trigger a refresh of the Card data from the server.
Viewed/Tapped Ordering#
In the CMS you can make Card collections be ordered based on viewed or tapped status, so that once a Card is viewed/tapped, the next Card from the collection will be shown to the user. This will enable users to always see fresh content.
Theming#
Card appearance and behavior are primarily configured directly within the Storyteller CMS for each Card Collection. The following properties can be configured in the CMS and influence the Card's presentation and behaviour:
Button Behavior#
- Button positioning: Buttons are optional visual elements that follow the
textOverContentproperty: - When
textOverContent = true: Button appears on the card (overlaying the content), positioned below the title/subtitle - When
textOverContent = false: Button appears below the card (below the title/subtitle section) - Button functionality: Buttons do not change the tappability of Cards - the entire card remains tappable and executes the same action as the button when tapped
-
Button text: The button text is defined in the Card data, not the theme
-
style.textLengthMode(default:truncate): How text that exceeds the available space is handled. truncate: Display text at the specified size; truncate with an ellipsis (...) if it doesn't fit.resize: Start at the specified text size and reduce the font size until the text fits (up to two lines for heading and subheading).style.textAlignment(default:start): Horizontal alignment of the heading and subheading. Can bestart,center, orend.style.padding(default:12): Inner padding around the text content. For full-bleed cards (marginHorizontal = 0) with text below the image and all cards with text on the image, padding is applied to all sides of the text. For cards with text below the image wheremarginHorizontal > 0, padding is applied only to the top and bottom of the text.style.marginHorizontal(default:0): Horizontal margin around the card.0means full-bleed.style.cornerRadius(default:{theme.primitives.cornerRadius}): Corner radius of the card. The application depends onmarginHorizontaland text position. Not applied for full-bleed cards (marginHorizontal=0) with text below the image. Applied to the image for cards with text below the image andmarginHorizontal > 0. Applied to the whole card for cards with text on the image andmarginHorizontal > 0.style.heading.font(default:{theme.customFont}): Font family for the heading.style.heading.textSize(default:22): Font size for the heading.style.heading.lineHeight(default:28): Line height for the heading.style.heading.textCase(default:default): Text case transformation (upper,lower,default).style.heading.letterSpacing(default:0): Letter spacing for the heading.style.heading.textColor(default:{theme.colors.white.primary}): Text color for the heading. The default applies when text is displayed on the background asset. When text is displayed below the background, the default color is{theme.colors.black.primary}in light mode and{theme.colors.white.primary}in dark mode.style.subHeading.font(default:{theme.customFont}): Font family for the subheading.style.subHeading.textSize(default:16): Font size for the subheading.style.subHeading.lineHeight(default:21): Line height for the subheading.style.subHeading.textCase(default:default): Text case transformation (upper,lower,default).style.subHeading.letterSpacing(default:0): Letter spacing for the subheading.style.subHeading.textColor(default:{theme.colors.white.secondary}): Text color for the subheading. The default applies when text is displayed on the background asset. When text is displayed below the background, the default color is{theme.colors.black.secondary}in light mode and{theme.colors.white.secondary}in dark mode.
Button Theme Properties#
style.button.title.font(default: uses heading font): Font family for the button text. If not specified or null, uses the heading font with the button's text size and line height.style.button.title.textSize(default:16): Font size for the button text.style.button.title.lineHeight(default:21): Line height for the button text.style.button.title.textCase(default:default): Text case transformation for the button text (upper,lower,default).style.button.title.letterSpacing(default:0): Letter spacing for the button text.style.button.title.textColor(default:{theme.colors.white.primary}for text on image,{theme.colors.black.primary}for text below image in light mode,{theme.colors.white.primary}for text below image in dark mode): Text color for the button. If not specified or null, uses the same color logic as the outline color.style.button.backgroundColor(optional): Background color of the button. If not set, the button will have a transparent background with an outline.style.button.outlineColor(default:{theme.colors.white.primary}for text on image,{theme.colors.black.primary}for text below image in light mode,{theme.colors.white.primary}for text below image in dark mode): Color of the button outline/border.style.button.outlineWidth(default:1): Width of the button outline/border in points.style.button.cornerRadius(optional, default:{theme.primitives.cornerRadius}): Corner radius of the button. If null or not set, falls back to the theme's default corner radius.style.button.textAlignment(default: uses cardtextAlignment): Text alignment for the button text. If not specified or null, uses the card's text alignment setting.
Behavior Properties#
behavior.reloading.reloadOnExit(default:true): Whether the Card Collection reloads after returning from tapping a Card (e.g., after dismissing the Story/Clip Player).behavior.reloading.reloadOnForeground(default:true): Whether the Card Collection reloads when the app comes to the foreground.