28 KiB
Settings View Styling Guide
A comprehensive guide to achieving the polished casino-style settings interface with golden accents, proper section separation, and consistent styling.
Table of Contents
- Overview
- The Complete Stack
- Step 1: Set Up Design Constants
- Step 2: Use SheetContainerView
- Step 3: Structure with SheetSection
- Step 4: Use Proper Settings Components
- Step 5: Apply Consistent Colors
- Complete Example
- Color Reference
- Troubleshooting
Overview
The polished casino settings interface you see in Blackjack and Baccarat is achieved through a combination of:
- SheetContainerView - Dark background, proper navigation bar styling
- SheetSection - Icon + title headers with card-like content containers
- CasinoKit components - Pre-styled toggles, pickers, and selectable rows
- Color.Sheet constants - Consistent golden accent and background colors
- Design constants - Standardized spacing, corner radius, and opacity values
Key Styling Elements
- Golden accent color:
Color.Sheet.accent = Color(red: 0.9, green: 0.75, blue: 0.3) - Dark blue background:
Color.Sheet.background = Color(red: 0.08, green: 0.12, blue: 0.18) - Section cards: White opacity fill with rounded corners
- Radio buttons: Golden checkmark circles for selected items
- Proper spacing: xxLarge (24pt) between sections
The Complete Stack
Required Components from CasinoKit
import CasinoKit
Views:
SheetContainerView- Outer container with navigation and dark backgroundSheetSection- Section container with icon/title header and cardSelectableRow- Radio button rows for pickersSelectionIndicator- Golden checkmark circlesSettingsToggle- Toggle switches with titles and subtitlesSpeedPicker,VolumePicker,BalancePicker- Specialized pickersBadgePill- Badge for displaying values like "$10 - $1,000"
Constants:
CasinoDesign- Spacing, corner radius, opacity, font sizes, etc.Color.Sheet- Sheet-specific colors (background, accent, etc.)
Local Design Constants Setup
Create a DesignConstants.swift file in your app that imports and typealias CasinoKit constants:
import SwiftUI
import CasinoKit
enum Design {
// Import shared constants via typealias
typealias Spacing = CasinoDesign.Spacing
typealias CornerRadius = CasinoDesign.CornerRadius
typealias LineWidth = CasinoDesign.LineWidth
typealias Shadow = CasinoDesign.Shadow
typealias Opacity = CasinoDesign.Opacity
typealias Animation = CasinoDesign.Animation
typealias BaseFontSize = CasinoDesign.BaseFontSize
typealias IconSize = CasinoDesign.IconSize
// Your game-specific constants
enum Size {
static let cardWidth: CGFloat = 90
// ... other game-specific sizes
}
}
This allows you to write Design.Spacing.large instead of CasinoDesign.Spacing.large throughout your app.
Step 1: Set Up Design Constants
1.1 Create DesignConstants.swift
Create a file called DesignConstants.swift in your app's Theme/ folder:
//
// DesignConstants.swift
// YourGame
//
import SwiftUI
import CasinoKit
enum Design {
// MARK: - Shared Constants (from CasinoKit)
typealias Spacing = CasinoDesign.Spacing
typealias CornerRadius = CasinoDesign.CornerRadius
typealias LineWidth = CasinoDesign.LineWidth
typealias Shadow = CasinoDesign.Shadow
typealias Opacity = CasinoDesign.Opacity
typealias Animation = CasinoDesign.Animation
typealias Scale = CasinoDesign.Scale
typealias MinScaleFactor = CasinoDesign.MinScaleFactor
typealias BaseFontSize = CasinoDesign.BaseFontSize
typealias IconSize = CasinoDesign.IconSize
// MARK: - Game-Specific Sizes
enum Size {
// Add your game-specific sizes here
static let cardWidth: CGFloat = 90
}
}
// MARK: - App Colors
extension Color {
// Import CasinoKit table colors
typealias Table = CasinoTable
// Add game-specific colors here
}
1.2 Why This Matters
By typealiasing CasinoDesign, you get:
- Consistent spacing throughout your app
- Proper opacity values for layering
- Standardized corner radii
- Matching font sizes
Don't hardcode values! Use constants instead:
❌ Bad:
.padding(24)
.opacity(0.1)
.cornerRadius(12)
✅ Good:
.padding(Design.Spacing.xxLarge)
.opacity(Design.Opacity.subtle)
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
Step 2: Use SheetContainerView
2.1 Basic Structure
SheetContainerView provides the foundation: dark background, navigation bar, and done button.
import SwiftUI
import CasinoKit
struct SettingsView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
SheetContainerView(
title: "Settings",
content: {
// Your sections go here
},
onCancel: nil, // Optional cancel button
onDone: {
dismiss()
},
doneButtonText: "Done"
)
}
}
2.2 What SheetContainerView Provides
Automatic Styling:
- Dark blue background:
Color.Sheet.background - NavigationStack with inline title
- Golden "Done" button:
Color.Sheet.accent - Optional "Cancel" button
- Toolbar with proper dark scheme
- ScrollView with proper vertical spacing
Key Parameters:
title- The navigation bar titlecontent- A@ViewBuilderclosure for your sectionsonCancel- Optional cancel action (if nil, no cancel button)onDone- Done button actiondoneButtonText- Text for done button (default: "Done")cancelButtonText- Text for cancel button (default: "Cancel")
2.3 Section Spacing
SheetContainerView automatically adds xxLarge (24pt) spacing between sections:
VStack(spacing: CasinoDesign.Spacing.xxLarge) {
content // Your sections
}
This creates the clean separation you see between sections.
Step 3: Structure with SheetSection
3.1 Basic SheetSection
Each logical group of settings should be in a SheetSection:
SheetSection(title: "DISPLAY", icon: "eye") {
// Settings content here
}
3.2 What SheetSection Provides
Header:
- Icon in golden color with opacity
- Title in uppercase, bold, rounded font
- Subtle spacing and padding
Content Card:
- White opacity background (
Color.Sheet.sectionFill) - Rounded corners (
CornerRadius.large) - Proper padding
- Horizontal margins
Visual Structure:
┌─────────────────────────────────────┐
│ 👁 DISPLAY │ ← Header (icon + title)
│ ┌─────────────────────────────────┐ │
│ │ │ │ ← Content card
│ │ [Settings content here] │ │ (white opacity fill)
│ │ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
3.3 Section Best Practices
Use semantic grouping:
- GAME STYLE - Game variants and modes
- RULES - Rule customization
- TABLE LIMITS - Betting limits
- DECK SETTINGS - Shoe configuration
- STARTING BALANCE - Balance picker
- DISPLAY - Visual settings
- SOUND - Audio settings
- DATA - Reset and export
Choose appropriate icons:
SheetSection(title: "GAME STYLE", icon: "suit.club.fill")
SheetSection(title: "RULES", icon: "list.bullet.clipboard")
SheetSection(title: "TABLE LIMITS", icon: "banknote")
SheetSection(title: "DECK SETTINGS", icon: "rectangle.portrait.on.rectangle.portrait")
SheetSection(title: "STARTING BALANCE", icon: "dollarsign.circle")
SheetSection(title: "DISPLAY", icon: "eye")
SheetSection(title: "SOUND", icon: "speaker.wave.2.fill")
SheetSection(title: "DATA", icon: "externaldrive")
Step 4: Use Proper Settings Components
4.1 SelectableRow (Radio Button Rows)
Use SelectableRow for picker options with radio buttons:
SheetSection(title: "DECK SETTINGS", icon: "rectangle.portrait.on.rectangle.portrait") {
VStack(spacing: Design.Spacing.small) {
SelectableRow(
title: "1 Deck",
subtitle: "Single deck, higher variance",
isSelected: deckCount == 1,
accentColor: Color.Sheet.accent,
action: { deckCount = 1 }
)
SelectableRow(
title: "6 Decks",
subtitle: "Standard casino shoe",
isSelected: deckCount == 6,
accentColor: Color.Sheet.accent,
action: { deckCount = 6 }
)
}
}
What SelectableRow Provides:
- Title in white, large font
- Subtitle in white with medium opacity
- Optional badge (e.g., "$10 - $1,000")
- Golden checkmark circle when selected
- Outlined circle when not selected
- Golden border when selected
- Subtle golden background fill when selected
- Proper padding and rounded corners
With Badge:
SelectableRow(
title: "Low Stakes",
subtitle: "Standard mini table",
isSelected: true,
accentColor: Color.Sheet.accent,
badge: {
BadgePill(text: "$10 - $1,000", isSelected: true)
},
action: { }
)
4.2 SettingsToggle (Toggle Switches)
Use SettingsToggle for on/off options:
SheetSection(title: "DISPLAY", icon: "eye") {
VStack(spacing: Design.Spacing.small) {
SettingsToggle(
title: "Show Animations",
subtitle: "Card dealing animations",
isOn: $showAnimations,
accentColor: Color.Sheet.accent
)
Divider().background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(
title: "Show Hints",
subtitle: "Basic strategy suggestions",
isOn: $showHints,
accentColor: Color.Sheet.accent
)
}
}
What SettingsToggle Provides:
- Title in large font, white
- Subtitle in body font, white with medium opacity
- Toggle switch with golden accent color
- Proper vertical alignment
- Minimum touch target height (44pt)
4.3 Specialized Pickers
SpeedPicker:
SpeedPicker(speed: $dealingSpeed, accentColor: Color.Sheet.accent)
VolumePicker:
VolumePicker(volume: $soundVolume, accentColor: Color.Sheet.accent)
BalancePicker:
BalancePicker(balance: $startingBalance, accentColor: Color.Sheet.accent)
4.4 Dividers Between Items
Use dividers to separate items within a section:
VStack(spacing: Design.Spacing.small) {
SettingsToggle(title: "Option 1", isOn: $option1, accentColor: accent)
Divider()
.background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(title: "Option 2", isOn: $option2, accentColor: accent)
}
Divider opacity levels:
Design.Opacity.hint(0.2) - Very subtle, recommendedDesign.Opacity.subtle(0.1) - Almost invisibleDesign.Opacity.light(0.3) - More prominent
Step 5: Apply Consistent Colors
5.1 Use Color.Sheet Constants
Always use Color.Sheet.accent for highlights:
private let accent = Color.Sheet.accent
// Then use it everywhere:
SelectableRow(..., accentColor: accent)
SettingsToggle(..., accentColor: accent)
SpeedPicker(..., accentColor: accent)
5.2 Color Constants Available
From CasinoKit:
Color.Sheet.background // Dark blue: Color(red: 0.08, green: 0.12, blue: 0.18)
Color.Sheet.accent // Gold: Color(red: 0.9, green: 0.75, blue: 0.3)
Color.Sheet.sectionFill // White.opacity(0.1)
Color.Sheet.cardBackground // White.opacity(0.05)
Color.Sheet.secondaryText // White.opacity(0.6)
Color.Sheet.cancelText // White.opacity(0.7)
5.3 Text Colors
Primary text (titles):
.foregroundStyle(.white)
Secondary text (subtitles, descriptions):
.foregroundStyle(.white.opacity(Design.Opacity.medium))
// or
.foregroundStyle(Color.Sheet.secondaryText)
Accent highlights:
.foregroundStyle(Color.Sheet.accent)
5.4 Background Colors
Section card fill:
.background(
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
.fill(Color.Sheet.sectionFill)
)
Selection highlight:
.background(
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.fill(isSelected ? accent.opacity(Design.Opacity.subtle) : .clear)
)
Complete Example
Here's a complete settings view demonstrating all the concepts:
//
// SettingsView.swift
// YourGame
//
import SwiftUI
import CasinoKit
struct SettingsView: View {
@Bindable var settings: GameSettings
@Environment(\.dismiss) private var dismiss
@State private var showClearDataAlert = false
/// Accent color for settings components
private let accent = Color.Sheet.accent
var body: some View {
SheetContainerView(
title: "Settings",
content: {
// 1. Table Limits
SheetSection(title: "TABLE LIMITS", icon: "banknote") {
TableLimitsPicker(selection: $settings.tableLimits)
}
// 2. Deck Settings
SheetSection(title: "DECK SETTINGS", icon: "rectangle.portrait.on.rectangle.portrait") {
DeckCountPicker(selection: $settings.deckCount)
}
// 3. Starting Balance
SheetSection(title: "STARTING BALANCE", icon: "dollarsign.circle") {
BalancePicker(balance: $settings.startingBalance, accentColor: accent)
}
// 4. Display
SheetSection(title: "DISPLAY", icon: "eye") {
VStack(spacing: Design.Spacing.small) {
SettingsToggle(
title: "Show Animations",
subtitle: "Card dealing animations",
isOn: $settings.showAnimations,
accentColor: accent
)
if settings.showAnimations {
Divider()
.background(Color.white.opacity(Design.Opacity.hint))
SpeedPicker(speed: $settings.dealingSpeed, accentColor: accent)
}
Divider()
.background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(
title: "Show Hints",
subtitle: "Strategy suggestions",
isOn: $settings.showHints,
accentColor: accent
)
}
}
// 5. Sound
SheetSection(title: "SOUND", icon: "speaker.wave.2.fill") {
VStack(spacing: Design.Spacing.small) {
SettingsToggle(
title: "Sound Effects",
subtitle: "Play game sounds",
isOn: $settings.soundEnabled,
accentColor: accent
)
if settings.soundEnabled {
Divider()
.background(Color.white.opacity(Design.Opacity.hint))
VolumePicker(volume: $settings.soundVolume, accentColor: accent)
}
Divider()
.background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(
title: "Haptics",
subtitle: "Vibration feedback",
isOn: $settings.hapticsEnabled,
accentColor: accent
)
}
}
// 6. Data
SheetSection(title: "DATA", icon: "externaldrive") {
Button {
showClearDataAlert = true
} label: {
HStack {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text("Clear All Data")
.font(.system(size: Design.BaseFontSize.large, weight: .semibold))
.foregroundStyle(.red)
Text("Reset progress and statistics")
.font(.system(size: Design.BaseFontSize.body))
.foregroundStyle(.white.opacity(Design.Opacity.medium))
}
Spacer()
Image(systemName: "trash")
.foregroundStyle(.red)
}
.padding()
.background(
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.fill(.clear)
)
.overlay(
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.strokeBorder(Color.white.opacity(Design.Opacity.subtle), lineWidth: Design.LineWidth.thin)
)
}
.buttonStyle(.plain)
}
// Version info
Text("YourGame v1.0 (1)")
.font(.system(size: Design.BaseFontSize.body))
.foregroundStyle(.white.opacity(Design.Opacity.light))
.frame(maxWidth: .infinity)
.padding(.top, Design.Spacing.large)
.padding(.bottom, Design.Spacing.medium)
},
onCancel: nil,
onDone: {
settings.save()
dismiss()
}
)
.alert("Clear All Data?", isPresented: $showClearDataAlert) {
Button("Cancel", role: .cancel) { }
Button("Clear", role: .destructive) {
// Clear data logic
}
} message: {
Text("This will delete all saved progress and statistics. This cannot be undone.")
}
}
}
// MARK: - Custom Pickers
struct TableLimitsPicker: View {
@Binding var selection: TableLimits
var body: some View {
VStack(spacing: Design.Spacing.small) {
ForEach(TableLimits.allCases) { limit in
SelectableRow(
title: limit.displayName,
subtitle: limit.description,
isSelected: selection == limit,
accentColor: Color.Sheet.accent,
badge: {
BadgePill(
text: "$\(limit.minBet) - $\(limit.maxBet)",
isSelected: selection == limit
)
},
action: { selection = limit }
)
}
}
}
}
struct DeckCountPicker: View {
@Binding var selection: Int
var body: some View {
VStack(spacing: Design.Spacing.small) {
ForEach([1, 2, 4, 6, 8], id: \.self) { count in
SelectableRow(
title: "\(count) Deck\(count == 1 ? "" : "s")",
subtitle: subtitleFor(count: count),
isSelected: selection == count,
accentColor: Color.Sheet.accent,
action: { selection = count }
)
}
}
}
private func subtitleFor(count: Int) -> String {
switch count {
case 1: return "Single deck, higher variance"
case 2: return "Lower house edge"
case 4: return "Common shoe game"
case 6: return "Standard casino"
case 8: return "Maximum penetration"
default: return ""
}
}
}
Color Reference
Complete Color.Sheet Definitions
From CasinoKit/Sources/CasinoKit/Theme/CasinoDesign.swift:
public extension Color {
enum Sheet {
/// Dark background for sheets and popups.
public static let background = Color(red: 0.08, green: 0.12, blue: 0.18)
/// Subtle fill for section cards.
public static let sectionFill = Color.white.opacity(CasinoDesign.Opacity.subtle)
/// Card background in settings/sheets.
public static let cardBackground = Color.white.opacity(CasinoDesign.Opacity.verySubtle)
/// Accent color for buttons and highlights (gold).
public static let accent = Color(red: 0.9, green: 0.75, blue: 0.3)
/// Secondary text color.
public static let secondaryText = Color.white.opacity(CasinoDesign.Opacity.accent)
/// Cancel button color.
public static let cancelText = Color.white.opacity(CasinoDesign.Opacity.strong)
}
}
Opacity Reference
public enum Opacity {
public static let verySubtle: Double = 0.05 // Barely visible backgrounds
public static let subtle: Double = 0.1 // Section fills
public static let selection: Double = 0.15 // Selection highlights
public static let hint: Double = 0.2 // Dividers, borders
public static let quarter: Double = 0.25 // Quarter opacity
public static let light: Double = 0.3 // Light text, borders
public static let overlay: Double = 0.4 // Modal overlays
public static let medium: Double = 0.5 // Subtitles, secondary info
public static let secondary: Double = 0.5 // Same as medium
public static let disabled: Double = 0.5 // Disabled controls
public static let accent: Double = 0.6 // Accent text
public static let strong: Double = 0.7 // Cancel buttons
public static let heavy: Double = 0.8 // Icons, strong text
public static let nearOpaque: Double = 0.85 // Almost solid
public static let almostFull: Double = 0.9 // Very solid
}
Common Color Patterns
Selected row:
.background(
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.fill(accent.opacity(Design.Opacity.subtle)) // 0.1
)
.overlay(
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.strokeBorder(accent.opacity(Design.Opacity.medium), lineWidth: Design.LineWidth.thin) // 0.5
)
Unselected row:
.background(
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.fill(.clear)
)
.overlay(
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.strokeBorder(Color.white.opacity(Design.Opacity.subtle), lineWidth: Design.LineWidth.thin) // 0.1
)
Troubleshooting
Issue: Colors don't look golden
Problem: Accent colors appear white or wrong color.
Solution: Ensure you're passing Color.Sheet.accent to the accentColor parameter:
// ❌ Wrong
SelectableRow(..., accentColor: .yellow)
// ✅ Correct
private let accent = Color.Sheet.accent
SelectableRow(..., accentColor: accent)
Issue: Sections not separated properly
Problem: Sections appear cramped together.
Solution: Ensure you're using SheetContainerView which automatically adds xxLarge (24pt) spacing:
SheetContainerView(title: "Settings", content: {
SheetSection(title: "SECTION 1", icon: "icon1") { ... }
SheetSection(title: "SECTION 2", icon: "icon2") { ... }
// Auto-spaced by xxLarge (24pt)
})
Issue: Section headers not styled properly
Problem: Section headers missing icon, wrong font, or poor spacing.
Solution: Use SheetSection, not custom VStack:
// ❌ Wrong
VStack {
Text("DISPLAY")
// content
}
// ✅ Correct
SheetSection(title: "DISPLAY", icon: "eye") {
// content
}
Issue: Radio buttons not golden
Problem: Selection indicators are white or blue circles.
Solution: Pass accentColor: Color.Sheet.accent to SelectableRow:
SelectableRow(
title: "Option",
subtitle: "Description",
isSelected: true,
accentColor: Color.Sheet.accent, // ← Must include this
action: { }
)
Issue: Background is wrong color
Problem: Background is white, gray, or wrong shade of blue.
Solution: Use SheetContainerView which sets Color.Sheet.background automatically:
// ❌ Wrong
var body: some View {
NavigationStack {
ScrollView {
// sections
}
.background(Color.gray) // Wrong!
}
}
// ✅ Correct
var body: some View {
SheetContainerView(title: "Settings", content: {
// sections - background automatically applied
})
}
Issue: Dividers are too prominent or invisible
Problem: Dividers within sections are too thick or can't be seen.
Solution: Use white with Design.Opacity.hint (0.2):
Divider()
.background(Color.white.opacity(Design.Opacity.hint))
Issue: Text not readable
Problem: Text appears dark gray or hard to read.
Solution: Always use .foregroundStyle(.white) for primary text:
Text("Title")
.foregroundStyle(.white)
Text("Subtitle")
.foregroundStyle(.white.opacity(Design.Opacity.medium))
Issue: Spacing inconsistent
Problem: Some spacing is 10pt, some 12pt, some 15pt.
Solution: Always use Design.Spacing constants:
VStack(spacing: Design.Spacing.small) { // 8pt
// items
}
.padding(Design.Spacing.medium) // 12pt
Issue: Missing import
Problem: SheetContainerView, SelectableRow, or other components not found.
Solution: Add import CasinoKit to the top of your file:
import SwiftUI
import CasinoKit // ← Required for CasinoKit components
Issue: Design constants not available
Problem: Design.Spacing, Design.Opacity, etc. not found.
Solution: Create DesignConstants.swift with typealias to CasinoKit constants (see Step 1).
Quick Checklist
Use this checklist to ensure your settings view has the proper styling:
- Import CasinoKit at top of file
- Create local
DesignConstants.swiftwith typealiases - Wrap entire settings view in
SheetContainerView - Each logical group in a
SheetSectionwith icon and title - Use
SelectableRowfor radio button options - Use
SettingsTogglefor on/off switches - Pass
accentColor: Color.Sheet.accentto all components - Use
Design.Spacing.smallbetween items in a section - Use
Divider().background(Color.white.opacity(Design.Opacity.hint))between items - Use
.foregroundStyle(.white)for primary text - Use
.foregroundStyle(.white.opacity(Design.Opacity.medium))for secondary text - No hardcoded spacing, opacity, or corner radius values
- Version info at bottom with subtle opacity
Summary
The polished casino settings interface is achieved through:
- SheetContainerView for the dark background and navigation
- SheetSection for icon headers and card-like content containers
- SelectableRow for radio button pickers with golden checkmarks
- SettingsToggle for toggle switches
- Color.Sheet.accent for consistent golden highlights
- Design constants for spacing, opacity, and font sizes
- Proper text colors (white for primary, white with opacity for secondary)
- Subtle dividers with 0.2 opacity between items
Follow this guide and your settings will match the professional casino aesthetic! 🎰✨