Get started quickly with the Virtual Stadium Chat component by following this comprehensive integration guide. This guide covers the essential steps to integrate real-time messaging, bet sharing, flash bets, and AI-powered insights into your iOS application.
To integrate the Chat component:
This guide will walk you through each step with code examples and best practices.
Add the ChatView component to your SwiftUI view with minimal required configuration. This basic setup provides full chat functionality with real-time messaging.
Required Parameters:
userJWT String required
JWT authentication token required to initialize the UI SDK and authenticate the user.
channelId String required
The unique identifier of the chat channel to display.
supportedLanguage SupportedLanguage required
Language for localizing SDK content (e.g., .english, .spanish).
The userJWT, channelId, and supportedLanguage are minimum required parameters. All other parameters are optional and default to nil. See Configuration Parameters for additional options.
ContentView.swift:
import SwiftUI
import VirtualStadiumSDK
struct ContentView: View {
let userJWT = "<your-jwt-token>"
let channelId = "<your-channel-id>"
var body: some View {
ChatView(
userJWT: userJWT,
channelId: channelId,
matchStatusManager
The ChatView component supports various configuration options to customize appearance, behavior, and functionality.
userJWT String required — JWT authentication token required to initialize the UI SDK and authenticate the user.channelId String required — The unique identifier of the chat channel to display.supportedLanguage SupportedLanguage required — Language code for localizing SDK content and fetching localized feeds. Available options: .english, .spanish, .portuguese, etc.matchStatusManager MatchStatusManager? optional — Manager for controlling match status behavior. Default is nil. See Match Status.flashBetManager FlashBetManager? optional — Manager for handling Flash Bet events and interactions. Default is nil. See Flash Bet.insightsManager InsightsManager? optional — Manager for handling Bet insights functionality. Default is nil. See Bet Insights.callbacksVM ChatViewCallbacksVM? optional — A view model encapsulating all user interaction callbacks such as bet sharing, profile opening, tag selection, and support team contact. Default is nil. See Callbacks.analyticsProvider AnalyticsProvider? optional — Provider for logging analytics events throughout the SDK. Default is nil.onError ((Error) -> Void)? optional — Callback for handling SDK errors. Receives error details for logging or displaying to users.chatSettings ChatSettings optional — Customization settings for theming, fonts, icons, and UI behavior. Default is ChatSettings(). See Chat Settings.All user interaction callbacks are grouped into a single ChatViewCallbacksVM object and passed to ChatView via the callbacksVM parameter. This replaces the previous approach of passing individual callback parameters directly to the ChatView initializer.
public class ChatViewCallbacksVM: ObservableObject {
// Called when the user wants to share a bet — receives a completion handler
// to be called with the user's bet slips. If nil, the Share Bet button is hidden.
var onBetShareHandler: ((@escaping ([BetPayload]) -> Void) -> Void)?
// Called when a user copies a bet from the chat
Breaking Change
In a previous version of the SDK, callbacks were passed as individual parameters directly to the ChatView initializer:
// ❌ Old API — no longer supported
ChatView(
userJWT: jwt,
channelId: channelId,
supportedLanguage: .english,
onBetShareHandler: { completion in ... },
onCopyToBetslip:
Callbacks Implementation:
Flash Bet enables time-sensitive quick betting triggered by live match events. Users receive real-time betting opportunities with a countdown timer.
public class FlashBetManager: ObservableObject {
// Called when a FlashBet event is received — provides the full FlashBet model
public var onFlashBetReceived: ((FlashBet) -> Void)?
// Called when a user selects an outcome
public var onFlashBetOutcomeSelected: ((EventOutcome) -> Void)?
// Publisher for updating flash bet market data — accepts a full FlashBet model
public var updateFlashBetPublisher: PassthroughSubject
Deprecated API
The following properties are deprecated and will be removed in a future release. Migrate to the new API as soon as possible.
// ❌ Deprecated — use onFlashBetReceived instead
public var onFlashBetEventReceived: ((ReceivedEvent) -> Void)?
// ❌ Deprecated — use updateFlashBetPublisher instead
public var updateFlashBetEventPublisher: PassthroughSubject<FlashBetEventStatus, Never>?The deprecated API uses ReceivedEvent and FlashBetEventStatus which provide limited data. The new API uses the full FlashBet model which includes the message title, structured content, localization keys, and more.
Avoid Handling Both Callbacks Simultaneously
Both onFlashBetEventReceived (deprecated) and onFlashBetReceived are triggered for the same flash bet event. Implementing both callbacks at the same time will result in the event being handled twice, causing unexpected behavior such as duplicate market updates, duplicate bet placements, or conflicting UI states.
Always use only one of the two callbacks — prefer onFlashBetReceived for all new implementations.
Timeout Behavior If you don't provide a market within the configured timeout (default 60 seconds) of the flash bet trigger, the flash bet will automatically be canceled and move to the next event in the queue (if any).
The timeout duration can be configured via ChatSettings.generalSettings.flashBetTimeoutDuration.
Market suspension timeout (how long a flash bet is visible) can be configured in the moderation panel or via the moderation API.
public struct FlashBet {
public let title: String
public let message: [ContentValue]
public let boldEventInfoSection: [String]?
public let translationKey: String?
public let event: Event
public
Flash Bet Implementation:
If you are currently using the deprecated onFlashBetEventReceived and updateFlashBetEventPublisher, here is how to migrate to the new API:
Before (Deprecated):
flashBetManager.onFlashBetEventReceived = { receivedEvent in
let status = FlashBetEventStatus(
flashBetEvent: receivedEvent,
market: market
)
flashBetManager.updateFlashBetEventPublisher?.send(status)
}After (New API):
flashBetManager.onFlashBetReceived = { flashBet in
var updatedFlashBet = flashBet
updatedFlashBet.market = market
flashBetManager.updateFlashBetPublisher?.send(updatedFlashBet)
}The key differences are:
onFlashBetReceived provides the full FlashBet model instead of the limited ReceivedEventupdateFlashBetPublisher accepts a FlashBet directly — simply set market on the existing FlashBet and send it, instead of wrapping it in a FlashBetEventStatusUse market status to control flash bet behavior during different match scenarios. This allows you to pause, resume, or deactivate markets based on live events.
Market Status Control:
flashBetManager.onFlashBetReceived = { flashBet in
// Store the flash bet for later updates
currentFlashBet = flashBet
// Send with active market
var updatedFlashBet = flashBet
updatedFlashBet.market = Market(id: "1", name:
Bet Insights provides AI-powered betting suggestions based on Sportradar's real-time data feeds. The feature displays the most relevant markets and outcomes for the current match.
matchId via the moderation panel or APIImportant Notes
onNewInsightsReceived callback returns all available markets and outcomes from the feeduseClientData = true in OutcomesPayload to use your custom market/outcome namespublic class InsightsManager: ObservableObject {
// Callback triggered when new insights are received
public var onNewInsightsReceived: (([OutcomePayloadRequest]) -> Void)?
// Callback triggered when user clicks an insight outcome
public var onInsightOutcomeSelected: ((_ marketId: String, _ outcomeId: String) -> Void)?
// Publisher for sending updated insights to display
The useClientData flag in OutcomesPayload determines naming sources:
useClientData = true - Use your custom market and outcome namesuseClientData = false - Use names from Sportradar's feedExample:
The SDK provides this data for you to map to your betting system:
/**
* Request structure from the SDK insights feed
*/
public struct OutcomePayloadRequest: Codable {
public var eventId: String
public var marketId: String
public var outcomeId: String
public var specifier: SpecifierRequest?
public init(
eventId:
Your app builds this structure to provide insights data back to the SDK:
/**
* Payload you provide to the SDK
*/
public struct OutcomesPayload: Codable {
public var outcomes: [ClientInsightData] = []
public var useClientData: Bool = false
public init(
outcomes: [ClientInsightData] = [],
public struct InsightEvent: Codable {
public var id: String
public var teams: [InsightTeam]
public var isLive: Bool
public var sport: InsightSport
}
public struct InsightTeam: Codable {
public var id: String?
public struct InsightOutcome: Codable {
public var id: String
public var name: String
public var status: InsightStatus
public var competitor: String?
public var oddsDecimal: Double
public var odds: String
public var isSelected: Bool
}
public
Bet Insights Implementation:
Enable mock insights for testing without a real match. This is useful during development to test the UI and interaction flows.
let chatSettings = ChatSettings(
generalSettings: GeneralSettings(
useTestInsights: true
)
)
ChatView(
userJWT: jwt,
channelId: channelId,
insightsManager: insightsManager,
supportedLanguage: .english,
chatSettings: chatSettings
)Use a mock data provider to generate test insights: MockInsightsProvider.swift:
struct MockInsightsProvider {
static func getMockInsights(
from requests: [OutcomePayloadRequest]
Enable users to share their bet slips and copy bets from other community members. This creates a social betting experience where users can learn from and engage with each other's betting strategies.
Bet Sharing Example:
let callbacksVM = ChatViewCallbacksVM(
onBetShareHandler: { completion in
/**
* TODO(developer): Replace with your bet slip fetching logic
*/
Task {
let betSlips = await getBetSlipsFromDatabase()
completion(betSlips)
}
},
onCopyToBetslipHandler:
public struct BetPayload {
let id: String
let betSlipId: String
let betType: BetType
let currency: String
let combinedOdds: Odds
let stake: Stake?
let payout: Payout?
let cashOut: CashOut?
let bets: [Bet]
}
public enum BetType
public struct Bet {
let id: String
let betType: BetType
let event: BetEvent
let markets: [BetMarket]
let odds: Odds?
}
public struct BetEvent {
let id: String
let name: String?
let teams: [Team]
Control how match status affects theming for Flash Bets and Insights. The match status determines visual styling to indicate whether a match is live, pre-match, or if automatic detection is disabled.
public enum MatchStatusBehavior {
case live // Force live theming
case automatic // Automatic based on feed (default)
case disabled // Disable automatic status changes
}public class MatchStatusManager: ObservableObject {
public var matchStatus: CurrentValueSubject<MatchStatusBehavior, Never>?
public init()
public func clear()
}Match Status Control:
Customize the appearance and behavior of the Chat component through comprehensive settings options.
public struct ChatSettings {Basic ChatSettings Usage:
let chatSettings = ChatSettings(
generalSettings: GeneralSettings(
oddsType: .us,
presentedInBottomSheet: false,
flashBetTimeoutDuration: 60.0,
useTestInsights: false
),
themeSettings: ThemeSettings(
mainColorPaletteTheme: MainColorPaletteTheme()
)
Configure general behavior including odds format, presentation style, and feature flags.
public struct GeneralSettings {
public var oddsType: OddsType
public var presentedInBottomSheet: Bool
public var flashBetTimeoutDuration: Double
public var useTestInsights: Bool
public init(
oddsType: OddsType = .eu,
presentedInBottomSheet: Bool = false,
GeneralSettings Example:
let generalSettings = GeneralSettings(
oddsType: .us, // American odds format
presentedInBottomSheet: true, // Optimize for bottom sheet
flashBetTimeoutDuration: 90.0, // 90 second timeout
useTestInsights: false // Use real insights
)
let chatSettings = ChatSettings(
generalSettings: generalSettings
)Control all color aspects of the UI including general colors, Flash Bet theming, and Insights theming. You can customize colors for both pre-match and live states.
public struct ThemeSettings {
public var mainColorPaletteTheme: MainColorPaletteTheme
public var textColorTheme: TextColorTheme
public var chatViewTheme: ChatViewTheme
public var reactionsTheme: ReactionsTheme
public var errorAndInfoMessageTheme: ErrorAndInfoMessageTheme
public var messageInputTheme: MessageInputTheme
public var tabsAndNavigationTheme: TabsAndNavigationTheme
public
Custom Theme Example:
import SwiftUI
let customTheme = ThemeSettings(
mainColorPaletteTheme: MainColorPaletteTheme(
primaryColor: Color(hex: "1976D2"),
secondaryColor: Color(hex
Set custom fonts to match your application's branding and typography style.
public struct FontSettings {
public let fontUltraLight: UIFont
public let fontThin: UIFont
public let fontLight: UIFont
public let fontRegular: UIFont
public let fontMedium: UIFont
public let fontSemibold: UIFont
public let fontBold: UIFont
public let fontHeavy: UIFont
public
Custom Font Example:
guard let customFont = UIFont(name: "YourCustomFont-Regular", size: 16) else {
fatalError("Failed to load custom font")
}
let fontSettings = FontSettings(
fontRegular: customFont,
fontMedium: UIFont(name: "YourCustomFont-Medium", size:
Replace default icons with custom images to match your application's design system.
public struct IconSettings {Custom Icons Example:
let iconSettings = IconSettings(
chatIcons: ChatIconSettings(
sendIcon: UIImage(named: "custom_send"),
attachIcon: UIImage(named: "custom_attach"),
replyIcon: UIImage(named: "custom_reply")
)
Customize all text strings displayed in the SDK for complete localization control.
The TranslationSettings struct contains hundreds of customizable strings. See the full structure in the provided code for all available translations.
Custom Translations Example:
let translationSettings = TranslationSettings(
GeneralVip: "VIP",
GeneralReply: "Responder",
GeneralReport: "Reportar",
GeneralSubmit: "Enviar",
GeneralCancel: "Cancelar",
MessageInputPlaceholder: "Escreva uma mensagem",
BetViewStake: "Aposta",
BetViewReturn:
Here's a comprehensive example with all features enabled:
ComprehensiveChatExample.swift:
These parameters have been removed from the ChatView initializer. All callbacks must now be provided through ChatViewCallbacksVM.
import SwiftUI
import VirtualStadiumSDK
struct ContentView: View {
let jwt: String
let channelId: String
var body: some View {
ChatView(
userJWT: jwt,
channelId: channelId,
supportedLanguage: .english,
callbacksVM: makeCallbacksVM()
)
}
private func makeCallbacksVM() -> ChatViewCallbacksVM {
ChatViewCallbacksVM(
onBetShareHandler: { completion in
/**
* TODO(developer): Replace with your bet slip fetching logic
*/
Task {
let betSlips = await getBetSlipsFromDatabase()
completion(betSlips)
}
},
onCopyToBetslipHandler: { betPayload in
/**
* TODO(developer): Add bet to user's bet slip
*/
addToBetSlip(betPayload)
print("Bet copied to slip!")
},
onReachSupportTeamHandler: { reason in
/**
* TODO(developer): Open support interface
*/
print("Opening support. Reason: \(reason)")
},
onOpenProfileHandler: { userId in
/**
* TODO(developer): Open user profile
*/
print("Opening profile for user: \(userId)")
},
onTagSelectedHandler: { tag in
/**
* TODO(developer): Handle tag selection
*/
print("Tag selected: \(tag.channelName)")
}
)
}
}Hiding Buttons
onBetShareHandler is nil, the "Share Bet" button is automatically hidden from the UI.onReachSupportTeamHandler is nil, the "Contact Support" button is automatically hidden from error and infraction screens.import SwiftUI
import VirtualStadiumSDK
struct ChatWithFlashBet: View {
let jwt: String
let channelId: String
@StateObject private var flashBetManager = FlashBetManager()
var body: some View {
NavigationView {
ChatView(
userJWT: jwt,
channelId: channelId,
flashBetManager: flashBetManager,
supportedLanguage: .english
)
.navigationTitle("Chat")
}
.onAppear {
setupFlashBet()
}
}
private func setupFlashBet() {
// Handle flash bet events
flashBetManager.onFlashBetReceived = { flashBet in
print("Flash bet received: \(flashBet.messageId)")
print("Title: \(flashBet.title)")
Task {
do {
/**
* TODO(developer): Replace with your actual market fetching logic
*/
let market = try await fetchFlashBetMarket(
eventId: flashBet.event.srEventId ?? ""
)
// Update the flash bet with market data
var updatedFlashBet = flashBet
updatedFlashBet.market = market
flashBetManager.updateFlashBetPublisher?.send(updatedFlashBet)
} catch {
print("Failed to fetch flash bet market: \(error)")
}
}
}
// Handle outcome selection
flashBetManager.onFlashBetOutcomeSelected = { outcome in
placeBet(outcome: outcome)
print("User selected outcome: \(outcome.name)")
}
}
private func fetchFlashBetMarket(eventId: String) async throws -> Market {
/**
* TODO(developer): Replace with your actual API call
*/
return Market(
id: "market_123",
name: "Next Goal Scorer",
status: .active,
outcomes: [
EventOutcome(id: "1", name: "Player A", odd: "2.50"),
EventOutcome(id: "2", name: "Player B", odd: "3.20")
]
)
}
private func placeBet(outcome: EventOutcome) {
/**
* TODO(developer): Implement your bet placement logic
*/
print("Placing bet on outcome: \(outcome.name)")
}
}import SwiftUI
import VirtualStadiumSDK
import Combine
struct ChatWithInsights: View {
let jwt: String
let channelId: String
@StateObject private var insightsManager = InsightsManager()
@State private var cancellables = Set<AnyCancellable>()
var body: some View {
ChatView(
userJWT: jwt,
channelId:
Hiding Share Button
If onBetShareHandler is nil in ChatViewCallbacksVM, the "Share Bet" button will be automatically hidden from the UI.
import SwiftUI
import VirtualStadiumSDK
import Combine
struct ChatWithMatchStatus: View {
let jwt: String
let channelId: String
@StateObject private var matchStatusManager = MatchStatusManager()
var body: some View {
ChatView(
userJWT: jwt,
channelId: channelId,
matchStatusManager: matchStatusManager,
supportedLanguage: .english
)
.onAppear {
matchStatusManager.matchStatus?.send(.live)
}
}
}
// Update match status dynamically
matchStatusManager.matchStatus?.send(.automatic)
matchStatusManager.matchStatus?.send(.disabled)import SwiftUI
import VirtualStadiumSDK
import Combine
struct ComprehensiveChatView: View {
let jwt: String
let channelId: String
@StateObject private var flashBetManager = FlashBetManager()
@StateObject private var insightsManager = InsightsManager()
@StateObject private var matchStatusManager = MatchStatusManager()
@State private var showError = false
@