6.7 KiB
6.7 KiB
Tooltip System Refactoring
Overview
The tooltip management system has been refactored from game-specific implementations to a generic, reusable system in CasinoKit.
Problem
The original implementation had several issues:
- Verbose: Each game had duplicate
TooltipConfigstructs and complex state management - Not scalable: Adding new tooltips required significant boilerplate
- Repetitive logic: Similar code duplicated across Blackjack and Baccarat
- Difficult to maintain: Changes required updates in multiple places
Solution
Created a centralized TooltipManager class in CasinoKit that:
- Manages tooltip state generically
- Automatically handles dismiss tracking
- Ensures only one tooltip shows at a time
- Integrates seamlessly with
OnboardingState
New Components
1. TooltipConfig (CasinoKit)
A simple struct defining tooltip properties:
public struct TooltipConfig: Equatable {
public let key: String
public let message: String
public let icon: String
public let position: ContextualTooltip.TooltipPosition
}
2. TooltipManager (CasinoKit)
An @Observable class that manages tooltip lifecycle:
@Observable
@MainActor
public final class TooltipManager {
private let onboarding: OnboardingState
public var currentTooltip: TooltipConfig?
public func show(
key: String,
message: String,
icon: String = "lightbulb.fill",
position: ContextualTooltip.TooltipPosition = .bottom,
delay: TimeInterval = 1.0
)
public func dismiss()
}
Key Features:
- Automatic dismiss tracking via
didSetoncurrentTooltip - Delayed presentation with cancellation if hint already shown
- Respects
OnboardingState.shouldShowHint()checks - Single source of truth for current tooltip
3. dynamicTooltip View Modifier (CasinoKit)
A convenient view modifier that displays tooltips:
public extension View {
func dynamicTooltip(_ manager: TooltipManager) -> some View
}
Usage in Games
Before (Verbose)
// State variables for each tooltip
@State private var showBettingHint = false
@State private var showDealHint = false
@State private var showActionsHint = false
// Separate struct definition
private struct TooltipConfig {
let key: String
let message: String
let icon: String
let position: ContextualTooltip.TooltipPosition
}
// Complex state with tracking
@State private var currentTooltip: TooltipConfig? {
didSet {
if let oldValue = oldValue, oldValue.key != currentTooltip?.key {
state.onboarding.markHintShown(oldValue.key)
}
}
}
// Generic helper with lots of parameters
private func showTooltip(key: String, message: String, icon: String, position: ContextualTooltip.TooltipPosition, delay: TimeInterval) {
Task { @MainActor in
try? await Task.sleep(for: .seconds(delay))
guard state.onboarding.shouldShowHint(key) else { return }
withAnimation(.spring(duration: Design.Animation.springDuration)) {
currentTooltip = TooltipConfig(key: key, message: message, icon: icon, position: position)
}
}
}
// Complex overlay in view body
.overlay {
if let tooltip = currentTooltip {
VStack {
// ... positioning logic
ContextualTooltip(
message: tooltip.message,
icon: tooltip.icon,
position: tooltip.position,
onDismiss: {
state.onboarding.markHintShown(tooltip.key)
withAnimation(.spring(duration: Design.Animation.springDuration)) {
currentTooltip = nil
}
}
)
// ... more positioning
}
}
}
After (Clean)
// Single state variable
@State private var tooltipManager: TooltipManager?
// Initialize in onAppear
.onAppear {
tooltipManager = TooltipManager(onboarding: state.onboarding)
}
// Simple show calls
private func showBettingHint() {
tooltipManager?.show(
key: "bettingZone",
message: "Select a chip and tap here to bet",
icon: "hand.tap.fill",
position: .bottom,
delay: 1.0
)
}
// One-line view modifier
.dynamicTooltip(tooltipManager ?? TooltipManager(onboarding: state.onboarding))
Benefits
- Reduced code: ~70 lines of boilerplate eliminated per game
- Better scalability: Adding new tooltips is now trivial
- Single source of truth: All tooltip logic in CasinoKit
- Easier maintenance: Changes only need to be made in one place
- Reusable: Any new game can use
TooltipManagerwith minimal setup
Files Changed
CasinoKit
- Created:
Sources/CasinoKit/Models/TooltipManager.swift - Updated:
Sources/CasinoKit/Exports.swift(added TooltipManager export) - Updated:
README.md(added TooltipManager documentation)
Blackjack
- Updated:
Views/Game/GameTableView.swift- Removed local
TooltipConfigstruct - Removed
currentTooltip@State with didSet - Removed generic
showTooltip()helper - Added
@State private var tooltipManager: TooltipManager? - Simplified tooltip show methods
- Replaced complex overlay with
.dynamicTooltip()modifier
- Removed local
Baccarat
- Updated:
Views/Game/GameTableView.swift- Same changes as Blackjack
Migration Guide
To use TooltipManager in a new game:
-
Add state variable:
@State private var tooltipManager: TooltipManager? -
Initialize in onAppear:
.onAppear { tooltipManager = TooltipManager(onboarding: gameState.onboarding) } -
Apply view modifier:
.dynamicTooltip(tooltipManager ?? TooltipManager(onboarding: gameState.onboarding)) -
Show tooltips:
tooltipManager?.show( key: "uniqueKey", message: "Your hint text", icon: "lightbulb.fill", position: .bottom, delay: 1.0 )
Testing
Both Blackjack and Baccarat have been built and tested successfully with the new system.
# Blackjack
xcodebuild -workspace CasinoGames.xcworkspace -scheme Blackjack \
-configuration Debug -destination 'platform=iOS Simulator,id=...' build
# ✅ BUILD SUCCEEDED
# Baccarat
xcodebuild -workspace CasinoGames.xcworkspace -scheme Baccarat \
-configuration Debug -destination 'platform=iOS Simulator,id=...' build
# ✅ BUILD SUCCEEDED
Future Enhancements
Potential improvements:
- Tooltip queue: Allow queueing multiple tooltips
- Positioning hints: Auto-detect best position based on screen space
- Animation customization: Per-tooltip animation styles
- Analytics: Track which tooltips are most helpful