The Profile component displays user information, statistics, and betting activity within the Virtual Stadium community. It shows a user's shared bets, copy count, and engagement metrics, and is typically accessed through the Chat component's user interactions.
The Profile component provides a detailed view of a user's activity in the Virtual Stadium community:
This guide will walk you through each step with code examples and best practices.
Prerequisites
Managers.SDK.shared.initialize()The Profile component is independent from the Chat component. It can be displayed by only providing the userId parameter.
Required Parameters:
userId String required
The unique identifier of the user whose profile to display. This is typically provided by the Chat component's onOpenProfile callback.
Optional Parameters:
onCopyBet ((BetPayload) -> Void)? optional
Callback invoked when a user copies a bet from the profile. Returns the BetPayload to add to your bet slip.
onClose (() -> Void)? optional
Callback invoked when the user closes the profile screen. Use this to handle navigation back to the chat.
chatSettings ChatSettings optional
Customization settings for theming, fonts, icons, and translations. Should match the Chat component settings for visual consistency. Default: ChatSettings()
The Profile component is designed to overlay or navigate from the Chat component. Use state management to toggle its visibility based on user interactions.
import SwiftUI
import VirtualStadiumSDK
struct ContentView: View {
let jwt: String
let channelId: String
@State private var showProfile = false
@State private var selectedUserId = ""
var body: some View {
NavigationView {
ZStack {
// Chat component
ChatView(
userJWT: jwt,
channelId: channelId,
supportedLanguage: .english,
onCopyToBetslip: { betPayload in
// Handle bet copy
print("Bet copied: \(betPayload.id)")
},
onOpenProfile: { userId in
selectedUserId = userId
showProfile = true
},
chatSettings: ChatSettings()
)
// Profile overlay
if showProfile {
ProfileView(
userId: selectedUserId,
onCopyBet: { betPayload in
// Handle bet copy from profile
print("Bet copied from profile: \(betPayload.id)")
},
onClose: {
showProfile = false
selectedUserId = ""
},
chatSettings: ChatSettings()
)
.transition(.move(edge: .trailing))
}
}
.navigationTitle("Chat")
.animation(.easeInOut, value: showProfile)
}
}
}The Profile component displays the following information:
| Metric | Description |
|---|---|
| Bets Shared | Total number of bet slips the user has shared with the community |
| Bets Copied | Number of times other users have copied this user's bets |
For a cohesive user experience, use the same ChatSettings for both Chat and Profile components. This ensures consistent colors, fonts, and icons throughout your application.
Create a single ChatSettings instance and reuse it across both components. This maintains visual consistency and simplifies theme management.
Best Practice:
Using different ChatSettings for Chat and Profile will result in an inconsistent user experience. Always share the same configuration.
import SwiftUI
import VirtualStadiumSDK
struct ChatWithProfileView: View {
let jwt: String
let channelId: String
@State private var showProfile = false
@State private var selectedUserId = ""
// Shared settings for consistency
private let sharedSettings = ChatSettings(
generalSettings: GeneralSettings(
oddsType: .us,
presentedInBottomSheet: false
),
themeSettings: ThemeSettings(
mainColorPaletteTheme: MainColorPaletteTheme(
primaryColor: .blue,
secondaryColor: .orange
)
),
fontSettings: FontSettings(
fontRegular: UIFont(name: "CustomFont-Regular", size: 16)
)
)
var body: some View {
NavigationView {
ZStack {
ChatView(
userJWT: jwt,
channelId: channelId,
supportedLanguage: .english,
onOpenProfile: { userId in
selectedUserId = userId
showProfile = true
},
chatSettings: sharedSettings // Shared settings
)
if showProfile {
ProfileView(
userId: selectedUserId,
onCopyBet: { bet in
copyBetToSlip(bet)
},
onClose: {
showProfile = false
},
chatSettings: sharedSettings // Same settings
)
}
}
}
}
private func copyBetToSlip(_ bet: BetPayload) {
// Add to bet slip
print("Copying bet: \(bet.id)")
}
}Integrate with SwiftUI's native navigation for proper back stack management and deep linking support. This approach provides better control over navigation and state preservation.
Benefits:
import SwiftUI
import VirtualStadiumSDK
struct ChatNavigationView: View {
let jwt: String
let channelId: String
var body: some View {
NavigationView {
ChatView(
userJWT: jwt,
channelId: channelId,
supportedLanguage: .english,
onOpenProfile: { userId in
// Navigation handled by NavigationLink
},
chatSettings: ChatSettings()
)
.background(
NavigationLink(
destination: ProfileDestination(),
isActive: $showProfile
) {
EmptyView()
}
.hidden()
)
.navigationTitle("Chat")
}
}
}
struct ProfileDestination: View {
let userId: String
var body: some View {
ProfileView(
userId: userId,
onCopyBet: { bet in
// Handle bet copy
},
onClose: nil, // Navigation handled by back button
chatSettings: ChatSettings()
)
.navigationBarBackButtonHidden(false)
}
}Display the profile in a modal sheet for a more native iOS experience. This pattern is ideal when you want to maintain visibility of the chat in the background.
Use Cases:
import SwiftUI
import VirtualStadiumSDK
struct ChatWithSheetProfile: View {
let jwt: String
let channelId: String
@State private var showProfile = false
@State private var selectedUserId = ""
var body: some View {
NavigationView {
ChatView(
userJWT: jwt,
channelId: channelId,
supportedLanguage: .english,
onOpenProfile: { userId in
selectedUserId = userId
showProfile = true
},
chatSettings: ChatSettings()
)
.navigationTitle("Chat")
.sheet(isPresented: $showProfile) {
NavigationView {
ProfileView(
userId: selectedUserId,
onCopyBet: { bet in
copyBetToSlip(bet)
},
onClose: {
showProfile = false
},
chatSettings: ChatSettings()
)
.navigationTitle("Profile")
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
private func copyBetToSlip(_ bet: BetPayload) {
print("Copying bet: \(bet.id)")
}
}Use a full screen cover for an immersive profile experience. This is ideal for viewing detailed user statistics and bet history without distractions.
Use Cases:
import SwiftUI
import VirtualStadiumSDK
struct ChatWithFullScreenProfile: View {
let jwt: String
let channelId: String
@State private var showProfile = false
@State private var selectedUserId = ""
var body: some View {
NavigationView {
ChatView(
userJWT: jwt,
channelId: channelId,
supportedLanguage: .english,
onOpenProfile: { userId in
selectedUserId = userId
showProfile = true
},
chatSettings: ChatSettings()
)
.navigationTitle("Chat")
.fullScreenCover(isPresented: $showProfile) {
ProfileView(
userId: selectedUserId,
onCopyBet: { bet in
copyBetToSlip(bet)
},
onClose: {
showProfile = false
},
chatSettings: ChatSettings()
)
}
}
}
private func copyBetToSlip(_ bet: BetPayload) {
print("Copying bet: \(bet.id)")
}
}Here's a full implementation combining all best practices:
Includes:
This example demonstrates production-ready code with proper architecture and error handling.
import SwiftUI
import VirtualStadiumSDK
import Combine
class ChatProfileViewModel: ObservableObject {
@Published var showProfile = false
@Published var selectedUserId = ""
@Published var showError = false
@Published var errorMessage = ""
func openProfile(userId: String) {
guard !userId.isEmpty else {
errorMessage = "Invalid user ID"
showError = true
return
}
selectedUserId = userId
showProfile = true
}
func closeProfile() {
showProfile = false
selectedUserId = ""
}
func handleBetCopy(_ bet: BetPayload) {
// Add to bet slip
print("Bet copied: \(bet.id)")
// Show confirmation if needed
}
}
struct CompleteChatProfileView: View {
let jwt: String
let channelId: String
@StateObject private var viewModel = ChatProfileViewModel()
// Shared settings for consistency
private let sharedSettings: ChatSettings = {
let customTheme = ThemeSettings(
mainColorPaletteTheme: MainColorPaletteTheme(
primaryColor: .blue,
secondaryColor: .orange,
tertiaryColor: .green
),
chatViewTheme: ChatViewTheme(
scrollToBottomBackgroundColor: Color.blue.opacity(0.15),
loadingIndicatorColor: .blue
)
)
let customFont = FontSettings(
fontRegular: UIFont(name: "CustomFont-Regular", size: 16),
fontMedium: UIFont(name: "CustomFont-Medium", size: 16),
fontBold: UIFont(name: "CustomFont-Bold", size: 16)
)
return ChatSettings(
generalSettings: GeneralSettings(
oddsType: .us,
presentedInBottomSheet: false,
flashBetTimeoutDuration: 60.0,
useTestInsights: false
),
themeSettings: customTheme,
fontSettings: customFont
)
}()
var body: some View {
NavigationView {
ZStack {
// Main chat component
ChatView(
userJWT: jwt,
channelId: channelId,
supportedLanguage: .english,
onBetShareHandler: { completion in
Task {
let betSlips = await getBetSlips()
completion(betSlips)
}
},
onCopyToBetslip: { bet in
viewModel.handleBetCopy(bet)
},
onTagSelected: { tag in
handleTagSelection(tag)
},
onReachSupportTeam: { reason in
openSupport(reason: reason)
},
onOpenProfile: { userId in
viewModel.openProfile(userId: userId)
},
analyticsProvider: AnalyticsProvider(
onEventReceived: { eventType in
print("Event: \(eventType)")
},
onBetEventReceived: { eventType, betPayload, builderId in
print("Bet event: \(eventType), Bet: \(betPayload.id)")
}
),
onError: { error in
viewModel.errorMessage = error.localizedDescription
viewModel.showError = true
},
chatSettings: sharedSettings
)
.navigationTitle("Chat")
// Animated profile overlay
if viewModel.showProfile {
ProfileView(
userId: viewModel.selectedUserId,
onCopyBet: { bet in
viewModel.handleBetCopy(bet)
},
onClose: {
viewModel.closeProfile()
},
chatSettings: sharedSettings
)
.transition(.move(edge: .trailing))
.zIndex(1)
}
}
.animation(.easeInOut(duration: 0.3), value: viewModel.showProfile)
}
.alert("Error", isPresented: $viewModel.showError) {
Button("OK", role: .cancel) { }
} message: {
Text(viewModel.errorMessage)
}
}
// Helper methods
private func getBetSlips() async -> [BetPayload] {
/**
* TODO(developer): Fetch bet slips from your backend
*/
// Example: return await apiClient.fetchUserBetSlips()
return []
}
private func handleTagSelection(_ tag: Tag) {
/**
* TODO(developer): Handle tag selection
*/
print("Tag selected: \(tag.name)")
}
private func openSupport(reason: InfractionReason?) {
/**
* TODO(developer): Open support interface
*/
print("Opening support")
}
}
// Preview
struct CompleteChatProfileView_Previews: PreviewProvider {
static var previews: some View {
CompleteChatProfileView(
jwt: "test-jwt",
channelId: "test-channel"
)
}
}