- Create SelfieCamTheme.swift with rose/magenta-tinted surface colors matching app branding - Add App-prefixed typealiases (AppSurface, AppAccent, AppStatus, etc.) to avoid Bedrock conflicts - Add SettingsCard container for visual grouping of related settings - Update SettingsView with card containers and accent-colored section headers - Update all settings-related files to use new theme colors - Pro section uses warning color, Debug section uses error color for visual distinction
13 KiB
AI Implementation Guide
How This App Was Architected & Built
This project was developed following strict senior-level iOS engineering standards, with guidance from AI assistants acting as Senior iOS Engineers specializing in SwiftUI and modern Apple frameworks.
Guiding Principles (from AGENTS.md)
- Protocol-Oriented Programming (POP) first: All shared capabilities defined via protocols before concrete types
- MVVM-lite: Views are "dumb" — all logic lives in
@Observableview models - Bedrock Design System: Centralized design tokens, no magic numbers
- Full accessibility: Dynamic Type, VoiceOver labels/hints/traits/announcements
- Modern Swift & SwiftUI: Swift 6 concurrency,
@MainActor,foregroundStyle,clipShape(.rect),NavigationStack - Testable & reusable design: Protocols enable mocking and future package extraction
Architecture Overview
Shared/
├── DesignConstants.swift → Uses Bedrock design tokens
├── BrandingConfig.swift → App icon & launch screen config
├── Color+Extensions.swift → Ring light color presets
├── Models/
│ ├── CameraFlashMode.swift → Flash mode enum
│ ├── CameraHDRMode.swift → HDR mode enum
│ ├── PhotoQuality.swift → Photo quality settings
│ └── CapturedPhoto.swift → Photo data model
├── Protocols/
│ ├── RingLightConfigurable.swift → Border, color, brightness
│ ├── CaptureControlling.swift → Timer, grid, zoom, capture
│ └── PremiumManaging.swift → Subscription state
├── Premium/
│ └── PremiumManager.swift → RevenueCat integration
├── Services/
│ └── PhotoLibraryService.swift → Photo saving service
└── Storage/
└── SyncedSettings.swift → iCloud-synced settings
Features/
├── Camera/ → Main camera UI
│ ├── ContentView.swift → Screen coordinator
│ ├── Views/ → UI components
│ └── GridOverlay.swift → Rule of thirds
├── Settings/ → Configuration
│ ├── SettingsView.swift → Settings UI
│ └── SettingsViewModel.swift → Settings logic + sync
└── Paywall/ → Pro subscription flow
Key Implementation Decisions
1. Ring Light Effect
- Achieved using
RingLightOverlayview that creates a colored border around the camera preview - Border width controlled via user setting (10-120pt range)
- Multiple preset colors with premium custom color picker
- Adjustable opacity/brightness (10%-100%)
- Enabled/disabled toggle for quick access
2. Camera System
- Uses MijickCamera framework for SwiftUI-native camera handling
- Supports front and back camera switching
- Pinch-to-zoom with smooth interpolation
- Flash modes: Off, On, Auto (with premium flash sync)
- HDR mode support (premium feature)
- Photo quality settings (medium free, high premium)
3. Capture Enhancements
- Self-timer with countdown (3s free, 5s/10s premium)
- Post-capture preview with share functionality
- Auto-save option to Photo Library
- Front flash using screen brightness
- Camera Control button (iPhone 16+): Full press captures, light press locks focus/exposure
- Hardware shutter: Volume buttons trigger capture via
VolumeButtonObserver
4. Freemium Model
- Built with RevenueCat for subscription management
PremiumManagerwraps RevenueCat SDKPremiumGateutility for clean premium feature access- Settings automatically fall back to free defaults when not premium
5. iCloud Sync
- Uses Bedrock's CloudSyncManager for settings synchronization
SyncedSettingsmodel contains all user preferences- Debounced saves for slider values (300ms delay)
- Real-time sync status display in Settings
- Available to all users (not a premium feature)
6. Branding System
- Uses Bedrock's Branding module for launch screen and app icon
BrandingConfig.swiftdefines app-specific colors and symbolsLaunchBackground.colorsetmatches launch screen primary color- Animated launch with configurable duration and pattern style
- Icon generator available in DEBUG builds
Camera Control Button Integration
Overview
The app supports the Camera Control button on iPhone 16+ via AVCaptureEventInteraction (iOS 17.2+).
Files Involved
| File | Purpose |
|---|---|
Shared/Protocols/CaptureEventHandling.swift |
Protocol defining hardware capture event handling |
Features/Camera/Views/CaptureEventInteraction.swift |
AVCaptureEventInteraction wrapper and SwiftUI integration |
Features/Camera/Views/VolumeButtonObserver.swift |
Volume button capture support (legacy) |
Supported Hardware Events
| Event | Hardware | Action |
|---|---|---|
| Primary (full press) | Camera Control, Action Button | Capture photo |
| Secondary (light press) | Camera Control | Lock focus/exposure |
| Volume buttons | All iPhones | Capture photo |
Implementation Details
// CaptureEventInteractionView is added to the camera ZStack
CaptureEventInteractionView(
onCapture: { performCapture() },
onFocusLock: { locked in handleFocusLock(locked) }
)
// The interaction uses AVCaptureEventInteraction (iOS 17.2+)
AVCaptureEventInteraction(
primaryEventHandler: { phase in /* capture on .ended */ },
secondaryEventHandler: { phase in /* focus lock on .began/.ended */ }
)
Device Compatibility
- iPhone 16+: Full Camera Control button support (press + light press)
- iPhone 15 Pro+: Action button support (when configured for camera)
- All iPhones: Volume button shutter via
VolumeButtonObserver
Premium Feature Implementation
How Premium Gating Works
The app uses a centralized PremiumGate utility for consistent premium feature handling:
// In SettingsViewModel
var isMirrorFlipped: Bool {
get { PremiumGate.get(cloudSync.data.isMirrorFlipped, default: false, isPremium: isPremiumUnlocked) }
set {
guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
updateSettings { $0.isMirrorFlipped = newValue }
}
}
Premium Features List
| Feature | Free Value | Premium Value |
|---|---|---|
| Ring light colors | Pure White, Warm Cream | All presets + custom |
| Timer options | Off, 3s | Off, 3s, 5s, 10s |
| Photo quality | Medium | Medium, High |
| HDR mode | Off | Off, On, Auto |
| True mirror | Off | Configurable |
| Skin smoothing | Off | Configurable |
| Flash sync | Off | Configurable |
| Center stage | Off | Configurable |
Settings & Persistence
SyncedSettings Model
All user preferences are stored in a single SyncedSettings struct that syncs via iCloud:
- Ring light: size, color ID, custom color RGB, opacity, enabled
- Camera: position, flash mode, HDR mode, photo quality
- Display: mirror flip, skin smoothing, grid visible
- Capture: timer, capture mode, auto-save
- Premium features: flash sync, center stage
Debounced Saves
Slider values (ring size, opacity) use debounced saving to prevent excessive iCloud writes:
private func debouncedSave(key: String, action: @escaping () -> Void) {
debounceTask?.cancel()
debounceTask = Task {
try? await Task.sleep(for: .milliseconds(300))
guard !Task.isCancelled else { return }
action()
}
}
Branding Implementation
Files Involved
- BrandingConfig.swift - Defines app icon and launch screen configurations
- LaunchBackground.colorset - Asset catalog color matching primary brand color
- SelfieCamApp.swift - Wraps ContentView with AppLaunchView
Color Scheme
extension Color {
enum Branding {
static let primary = Color(red: 0.85, green: 0.25, blue: 0.45) // Vibrant magenta
static let secondary = Color(red: 0.45, green: 0.12, blue: 0.35) // Deep purple
static let accent = Color.white
}
}
Launch Screen Configuration
static let selfieCam = LaunchScreenConfig(
title: "SELFIE CAM",
tagline: "Look Your Best",
iconSymbols: ["camera.fill", "sparkles"],
cornerSymbol: "sparkle",
patternStyle: .radial,
// ... colors and sizing
)
Development Workflow
Adding a New Feature
- Define the protocol (if shared behavior)
- Add to SyncedSettings (if needs persistence)
- Implement in SettingsViewModel (with premium gating if applicable)
- Add UI in SettingsView
- Update documentation (README, this file)
Adding a Premium Feature
- Add setting to
SyncedSettingswith appropriate default - Use
PremiumGate.get()for the getter with free default - Use
PremiumGate.canSet()guard for the setter - Add premium indicator (crown icon) in UI
- Wire up paywall trigger for non-premium users
Testing Premium Features
Set environment variable in scheme:
- Name:
ENABLE_DEBUG_PREMIUM - Value:
1
Reusability & Extraction
The codebase is structured for future extraction into reusable packages:
| Potential Package | Contents |
|---|---|
| SelfieCameraKit | Camera views, capture logic, preview components |
| RingLightKit | Ring light overlay, color presets, configuration |
| PremiumKit | Premium manager, gating utilities, paywall |
| SyncedSettingsKit | CloudSyncManager, settings model pattern |
Key Dependencies
| Dependency | Purpose | Integration |
|---|---|---|
| Bedrock | Design system, branding, cloud sync | Local Swift package |
| MijickCamera | Camera capture and preview | SPM dependency |
| RevenueCat | Subscription management | SPM dependency |
Code Quality Standards
- No magic numbers: All values from Design constants
- Full accessibility: Every interactive element has VoiceOver support
- Protocol-first: Shared behavior defined via protocols
- Separation of concerns: Views are dumb, ViewModels contain logic
- Modern APIs: Swift 6, async/await, @Observable
- Documentation: Code comments, README, implementation guides
Known Issues / TODO
Camera Control Button Light Press - NOT WORKING
Status: ❌ Broken - Needs Investigation
The Camera Control button (iPhone 16+) full press works for photo capture, but the light press (secondary action) does NOT work.
Testing revealed that the "secondary" events in logs were actually triggered by volume button, not Camera Control light press. The volume button works because onCameraCaptureEvent handles all hardware capture buttons.
What Works:
- ✅ Camera Control full press → triggers photo capture
- ✅ Volume up/down → triggers secondary event (focus lock)
What Doesn't Work:
- ❌ Camera Control light press → no event received at all
- ❌ Camera Control swipe gestures (zoom) → Apple-exclusive API
User Action Required - Check Accessibility Settings:
Settings > Accessibility > Camera Control:
- Ensure Camera Control is enabled
- Ensure Light-Press is turned ON
- Adjust Light-Press Force if needed
- Check Double Light-Press Speed
These system settings may affect third-party apps differently than Apple Camera.
Investigation Areas:
-
Accessibility settings may block third-party light press
- User reports light press works in Apple Camera but not SelfieCam
- System may require explicit light-press enablement per-app
-
MijickCamera session configuration
- The third-party camera framework may interfere with light press detection
- MijickCamera manages its own AVCaptureSession - may conflict
- Try testing with raw AVCaptureSession to isolate the issue
-
onCameraCaptureEventsecondaryAction limitations- The
secondaryActionclosure receives volume button events correctly - Camera Control light press may use different event pathway
- Apple may internally route light press to their Camera app exclusively
- The
-
Light press may require AVCapturePhotoOutput configuration
- Secondary events might need specific photo output settings
- Check if
AVCapturePhotoSettingshas light-press related properties
-
Possible Apple restriction (most likely)
- Light press and swipe gestures appear restricted to first-party apps
- Similar to swipe-to-zoom which is Apple-exclusive
- No public API documentation confirms light press availability
Future Enhancements
Potential areas for expansion:
- Real-time filters (beauty, color grading)
- Gesture-based capture (smile detection)
- Widget for quick camera access
- Apple Watch remote trigger
- Export presets (aspect ratios, watermarks)
- Social sharing integrations
- Camera Control button swipe-to-zoom (if Apple makes API public)
This architecture demonstrates production-quality SwiftUI development while delivering a polished, competitive user experience.