- Add SETTINGS_GUIDE.md with comprehensive documentation for creating branded settings screens - Rename Color.Text to Color.TextColors to avoid conflict with SwiftUI Text view - Rename Color.Button to Color.ButtonColors to avoid conflict with SwiftUI Button view - Add accentColor parameter to SettingsSectionHeader for customizable section icons - Update README and PulsingModifier to use new TextColors typealias
12 KiB
Settings View Setup Guide
This guide explains how to create a branded settings screen using Bedrock's theming system and settings components.
Overview
Bedrock provides:
- Color protocols for consistent theming (
SurfaceColorProvider,AccentColorProvider, etc.) - Reusable settings components (
SettingsToggle,SettingsCard,SegmentedPicker, etc.) - Design constants for spacing, typography, and animations
By creating a custom theme, your app gets a unique visual identity while reusing all the settings UI components.
Step 1: Create Your App's Theme
Create a file called [AppName]Theme.swift in your app's Shared/ folder.
Define Surface Colors
Surface colors create visual depth and separation. Use a subtle tint that matches your brand:
import SwiftUI
import Bedrock
/// Surface colors with a subtle [brand]-tint.
public enum MyAppSurfaceColors: SurfaceColorProvider {
/// Primary background - darkest level
public static let primary = Color(red: 0.08, green: 0.06, blue: 0.10)
/// Secondary/elevated surface
public static let secondary = Color(red: 0.12, green: 0.08, blue: 0.14)
/// Tertiary/card surface - most elevated
public static let tertiary = Color(red: 0.16, green: 0.11, blue: 0.18)
/// Overlay background (for sheets/modals)
public static let overlay = Color(red: 0.10, green: 0.07, blue: 0.12)
/// Card/grouped element background
public static let card = Color(red: 0.14, green: 0.10, blue: 0.16)
/// Subtle fill for grouped content sections
public static let groupedFill = Color(red: 0.12, green: 0.09, blue: 0.14)
/// Section fill for list sections
public static let sectionFill = Color(red: 0.16, green: 0.12, blue: 0.18)
}
Define Accent Colors
These are your brand's primary interactive colors:
public enum MyAppAccentColors: AccentColorProvider {
/// Primary accent - your main brand color
public static let primary = Color(red: 0.85, green: 0.25, blue: 0.45)
/// Light variant
public static let light = Color(red: 0.95, green: 0.45, blue: 0.60)
/// Dark variant
public static let dark = Color(red: 0.65, green: 0.18, blue: 0.35)
/// Secondary accent for contrast
public static let secondary = Color(red: 1.0, green: 0.95, blue: 0.90)
}
Define Other Color Providers
Complete the theme with text, button, status, border, and interactive colors:
public enum MyAppTextColors: TextColorProvider {
public static let primary = Color.white
public static let secondary = Color.white.opacity(Design.Opacity.accent)
public static let tertiary = Color.white.opacity(Design.Opacity.medium)
public static let disabled = Color.white.opacity(Design.Opacity.light)
public static let placeholder = Color.white.opacity(Design.Opacity.overlay)
public static let inverse = Color.black
}
public enum MyAppButtonColors: ButtonColorProvider {
public static let primaryLight = Color(red: 0.95, green: 0.40, blue: 0.55)
public static let primaryDark = Color(red: 0.75, green: 0.20, blue: 0.40)
public static let secondary = Color.white.opacity(Design.Opacity.subtle)
public static let destructive = Color.red.opacity(Design.Opacity.heavy)
public static let cancelText = Color.white.opacity(Design.Opacity.strong)
}
public enum MyAppStatusColors: StatusColorProvider {
public static let success = Color(red: 0.2, green: 0.8, blue: 0.4)
public static let warning = Color(red: 1.0, green: 0.75, blue: 0.2)
public static let error = Color(red: 0.9, green: 0.3, blue: 0.3)
public static let info = Color(red: 0.5, green: 0.7, blue: 0.95)
}
public enum MyAppBorderColors: BorderColorProvider {
public static let subtle = Color.white.opacity(Design.Opacity.subtle)
public static let standard = Color.white.opacity(Design.Opacity.hint)
public static let emphasized = Color.white.opacity(Design.Opacity.light)
public static let selected = MyAppAccentColors.primary.opacity(Design.Opacity.medium)
}
public enum MyAppInteractiveColors: InteractiveColorProvider {
public static let selected = MyAppAccentColors.primary.opacity(Design.Opacity.selection)
public static let hover = Color.white.opacity(Design.Opacity.subtle)
public static let pressed = Color.white.opacity(Design.Opacity.hint)
public static let focus = MyAppAccentColors.light
}
Combine Into a Theme
public enum MyAppTheme: AppColorTheme {
public typealias Surface = MyAppSurfaceColors
public typealias Text = MyAppTextColors
public typealias Accent = MyAppAccentColors
public typealias Button = MyAppButtonColors
public typealias Status = MyAppStatusColors
public typealias Border = MyAppBorderColors
public typealias Interactive = MyAppInteractiveColors
}
Add Convenience Typealiases
Create top-level typealiases with an App prefix to avoid conflicts with Bedrock's defaults:
/// Short typealiases for cleaner usage throughout the app.
/// These avoid conflicts with Bedrock's default typealiases by using unique names.
///
/// Usage:
/// ```swift
/// .background(AppSurface.primary)
/// .foregroundStyle(AppAccent.primary)
/// ```
typealias AppSurface = MyAppSurfaceColors
typealias AppTextColors = MyAppTextColors
typealias AppAccent = MyAppAccentColors
typealias AppButtonColors = MyAppButtonColors
typealias AppStatus = MyAppStatusColors
typealias AppBorder = MyAppBorderColors
typealias AppInteractive = MyAppInteractiveColors
Important
: Do NOT add typealiases inside
extension Color { }as they will conflict with Bedrock's defaults and cause "ambiguous use" compiler errors. Use top-levelApp-prefixed typealiases instead.
Step 2: Create a Settings Card Container
Add a reusable card container for grouping related settings:
/// A card container that provides visual grouping for settings sections.
struct SettingsCard<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
content
}
.padding(Design.Spacing.medium)
.background(
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
.fill(AppSurface.card)
)
.overlay(
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
.strokeBorder(AppBorder.subtle, lineWidth: Design.LineWidth.thin)
)
}
}
Step 3: Build Your Settings View
Use Bedrock's components with your theme colors:
import SwiftUI
import Bedrock
struct SettingsView: View {
@Bindable var viewModel: SettingsViewModel
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: Design.Spacing.medium) {
// Section with header and card
SettingsSectionHeader(
title: "Appearance",
systemImage: "paintbrush",
accentColor: AppAccent.primary
)
SettingsCard {
SettingsToggle(
title: "Dark Mode",
subtitle: "Use dark appearance",
isOn: $viewModel.darkMode,
accentColor: AppAccent.primary
)
SegmentedPicker(
title: "Theme",
options: [("Light", "light"), ("Dark", "dark"), ("System", "system")],
selection: $viewModel.theme,
accentColor: AppAccent.primary
)
}
// Another section
SettingsSectionHeader(
title: "Notifications",
systemImage: "bell",
accentColor: AppAccent.primary
)
SettingsCard {
SettingsToggle(
title: "Push Notifications",
subtitle: "Receive alerts and updates",
isOn: $viewModel.pushEnabled,
accentColor: AppAccent.primary
)
}
Spacer(minLength: Design.Spacing.xxxLarge)
}
.padding(.horizontal, Design.Spacing.large)
}
.background(AppSurface.primary)
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Done") { dismiss() }
.foregroundStyle(AppAccent.primary)
}
}
}
}
}
Available Components
SettingsSectionHeader
A section header with optional icon and accent color.
SettingsSectionHeader(
title: "Account",
systemImage: "person.circle",
accentColor: Color.Accent.primary
)
SettingsToggle
A toggle row with title and subtitle.
SettingsToggle(
title: "Sound Effects",
subtitle: "Play sounds for events",
isOn: $viewModel.soundEnabled,
accentColor: Color.Accent.primary
)
SegmentedPicker
A horizontal capsule-style picker.
SegmentedPicker(
title: "Quality",
options: [("Low", 0), ("Medium", 1), ("High", 2)],
selection: $viewModel.quality,
accentColor: Color.Accent.primary
)
SelectableRow
A card-like row for option selection.
SelectableRow(
title: "Premium",
subtitle: "Unlock all features",
isSelected: plan == .premium,
accentColor: Color.Accent.primary,
badge: { BadgePill(text: "$9.99") }
) {
plan = .premium
}
VolumePicker
A slider with speaker icons for volume/percentage values.
VolumePicker(
label: "Volume",
volume: $viewModel.volume,
accentColor: Color.Accent.primary
)
SettingsRow
A navigation-style row with icon and chevron.
SettingsRow(
systemImage: "star.fill",
title: "Rate App",
iconColor: Color.Status.warning
) {
openAppStore()
}
BadgePill
A capsule badge for values or tags.
BadgePill(
text: "$4.99",
isSelected: isCurrentPlan,
accentColor: Color.Accent.primary
)
Color Relationship Guide
| Surface Level | Use Case | Visual Depth |
|---|---|---|
AppSurface.primary |
Main background | Darkest |
AppSurface.overlay |
Sheet/modal backgrounds | Slightly elevated |
AppSurface.card |
Settings cards | Distinct from background |
AppSurface.sectionFill |
Section containers | Most elevated |
| Accent Purpose | Color |
|---|---|
| Interactive elements | AppAccent.primary |
| Highlights | AppAccent.light |
| Pressed states | AppAccent.dark |
| Pro/Premium sections | AppStatus.warning |
| Debug/Error sections | AppStatus.error |
Tips
-
Derive surface colors from your brand: Add a subtle RGB tint (e.g., if brand is pink, surfaces should have a rose undertone).
-
Use consistent accent colors: Pass
accentColor: AppAccent.primaryto all Bedrock components. -
Group related settings: Wrap related toggles/pickers in a
SettingsCardfor visual hierarchy. -
Section-specific accents: Use
AppStatus.warningfor premium sections,AppStatus.errorfor debug. -
Test with Dynamic Type: Bedrock uses
Design.BaseFontSizevalues that scale properly. -
Avoid
Color.typealiases: UseApp-prefixed typealiases to prevent conflicts with Bedrock's defaults.
Example Apps
- SelfieCam: Rose/magenta theme with camera-focused settings
- SelfieRingLight: Similar structure with ring light controls
- CameraTester: Neutral theme for testing/debugging
See each app's [AppName]Theme.swift for implementation examples.