It all started with a simple idea: "What if we could turn any phone into a professional lighting studio?" Little did we know this would evolve into something much bigger.
# Core Light Features Identified: 1. Preset Effects β Girlish warm pink - Soft temperament β Skin-smoothing purple - Perfect skin β Cold white/blue - Transparent texture β Pure white - Natural uniform 2. Professional Controls β Split-screen capability β Brightness adjustment β Anti-flicker technology β Custom color support
Chosen for clean separation of UI and business logic, perfect for SwiftUI
ZStack { LightBackgroundLayer() // Color effects CameraPreviewLayer() // Optional camera ControlPanelLayer() // UI controls }
π± Main Screen Layout: ββββββββββββββββββββ β Light Panel β ββββββββββββββββββββ€ β Camera Preview β β Draggable β βοΈ β ββββββββββββββββββββ€ β Control Panel β β Collapsible ββββββββββββββββββββ
FillLight/ βββ App/ β βββ FillLightApp.swift β βββ Info.plist βββ Views/ β βββ ContentView.swift # Main container view β βββ CameraView.swift # Camera preview β βββ ControlPanelView.swift # Settings panel β βββ RecordingTimerView.swift # Recording UI β βββ Components/ β βββ RecordButton.swift # Custom camera button β βββ ColorPresetButton.swift # Color preset selector βββ Controls/ β βββ Camera/ β β βββ CameraService.swift # Camera handling β β βββ CameraAccessManager.swift β βββ Light/ β β βββ LightEffectService.swift β β βββ LightViewModel.swift β βββ Utils/ β βββ HapticManager.swift # Haptic feedback β βββ WindowInteractionManager.swift βββ Models/ β βββ LightEffect.swift β βββ ColorPreset.swift βββ Resources/ β βββ Localizable.xcstrings # Localization β βββ Assets.xcassets βββ Extensions/ βββ View+Extensions.swift # SwiftUI extensions
// Before: Everything in ContentView π± struct ContentView: View { @State private var brightness: Double = 0.5 @State private var currentColor: Color = .white @State private var cameraActive = false // ... 300+ lines of mixed concerns } // After: Clean separation of concerns β¨ struct ContentView: View { @StateObject private var viewModel = LightViewModel() @StateObject private var cameraService = CameraService.shared var body: some View { MainLayout(viewModel: viewModel) } }
// The singleton pattern for core services class CameraService: ObservableObject { static let shared = CameraService() private init() { } // Ensure single instance // Camera-specific logic only } class LightEffectService: ObservableObject { static let shared = LightEffectService() private init() { } // Light effect logic only }
// Reusable gesture handling struct DraggableModifier: ViewModifier { @Binding var position: CGPoint func body(content: Content) -> some View { content.gesture( DragGesture() .onChanged { value in position = value.location } ) } } // Usage CameraPreview() .modifier(DraggableModifier(position: $cameraPosition))
Each component should have one clear purpose - made debugging much easier!
Using ObservableObject for services and @Published for reactive updates was a game-changer
Breaking down complex views into smaller, reusable components improved maintainability
Singletons for services ensured consistent state across the app
Working with Cursor AI and Claude-3.5-sonnet transformed our development process:
Our journey had three major breakthroughs:
// Day 1: Just a color picker Rectangle() .fill(currentColor) .ignoresSafeArea() // Today: Professional lighting system ZStack { LightEffectLayer(effect: currentEffect) CameraPreviewLayer() ControlPanel() }
The game-changer moment came at 2 AM while debugging. Camera startup: 9 seconds β 1 second!
Added professional effects that creators love:
Started with a simple fill light concept. First challenge: UI layout for both iPad and iPhone.
// First attempt at adaptive layout struct ContentView: View { var body: some View { ZStack { Rectangle().fill(currentColor) ColorPicker() // Simple but effective } } }
Added camera integration - everything broke. Camera preview upside down, app freezing.
Camera initialization taking 9 seconds. Multiple backup restores as we tried different approaches.
π Finally fixed: Camera startup reduced to 1 second!
Major UI overhaul - another "zip backup" moment. Added draggable camera preview and effect controls.
Implementing split-screen mode seemed simple... until it wasn't.
// The solution that finally worked enum SplitStyle: CaseIterable { case horizontal case vertical case diagonal var iconName: String { switch self { case .horizontal: return "rectangle.split.2x1" case .vertical: return "rectangle.split.1x2" case .diagonal: return "rectangle.split.diagonal" } } }
Front camera worked perfectly. Back camera? Upside down!
Solution: Proper video orientation handling for each camera position
if currentPosition == .back { connection.videoOrientation = .landscapeRight connection.isVideoMirrored = false } else { connection.videoOrientation = .landscapeLeft connection.isVideoMirrored = true }
Adding dynamic light effects was like choreographing a light show.
How to create smooth, professional-looking effects without draining the battery?
Camera performance was inconsistent across devices. Time to get serious about logging.
// First attempt at logging print("Camera started") print("Error: \(error)") // Enhanced logging system func log(_ category: String, _ message: String, file: String = #file, line: Int = #line) { let timestamp = Date().formatted() print("π± [\(category)] [\(timestamp)] \(message) - \(file):\(line)") } // Usage log("CAMERA", "Session configuration started") log("CAMERA", "Input count: \(session.inputs.count)") log("CAMERA", "Output count: \(session.outputs.count)")
Everything looked perfect on iPhone. Then we tested on iPad...
struct ContentView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass var body: some View { Group { if horizontalSizeClass == .regular { iPadLayout() } else { iPhoneLayout() } } } } // Debug helper for layout issues extension View { func debugLayout() -> some View { self.overlay( GeometryReader { geometry in Color.clear.onAppear { print("π View size: \(geometry.size)") print("π± Safe area: \(geometry.safeAreaInsets)") } } ) } }
The app started crashing after extended camera use. Time for some memory debugging.
// Added memory tracking class CameraService { func logMemoryUsage() { var info = mach_task_basic_info() var count = mach_msg_type_number_t(MemoryLayout.size)/4 let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) { $0.withMemoryRebound(to: integer_t.self, capacity: 1) { task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count) } } if kerr == KERN_SUCCESS { log("MEMORY", "Used: \(info.resident_size/1024/1024) MB") } } }
π Memory leak fixed! Usage now stable at ~100MB
First submission rejected! Issues with:
Users reported random camera freezes when switching effects. Time for some serious debugging!
// Added comprehensive state logging func switchEffect(_ effect: LightEffect) { log("EFFECT", "β‘οΈ Switching to effect: \(effect)") log("STATE", "π± Current state:") log("STATE", " - Camera running: \(session.isRunning)") log("STATE", " - Active effect: \(currentEffect)") log("STATE", " - UI responding: \(isUIEnabled)") log("STATE", " - Memory usage: \(currentMemoryUsage)MB") }
// Added debounce to effect switching private var effectSwitchTask: Task? func switchEffect(_ effect: LightEffect) { effectSwitchTask?.cancel() effectSwitchTask = Task { try? await Task.sleep(nanoseconds: 100_000_000) // 100ms delay guard !Task.isCancelled else { return } await MainActor.run { self.currentEffect = effect } } }
Control panel randomly disappearing on device rotation. Another UI mystery to solve!
extension View { func debugTransitions() -> some View { self.modifier(DebugTransitionModifier()) } } struct DebugTransitionModifier: ViewModifier { func body(content: Content) -> some View { content .onAppear { log("VIEW", "π¬ View appeared") } .onDisappear { log("VIEW", "π View disappeared") } .onChange(of: UIDevice.current.orientation) { newOrientation in log("ROTATION", "π± Device rotated to: \(newOrientation)") log("FRAME", "Current frame: \(UIScreen.main.bounds)") } } }
Light effects causing frame drops on older devices. Time to optimize!
class PerformanceMonitor { static let shared = PerformanceMonitor() private var frameTimestamps: [TimeInterval] = [] func logFrameTime() { frameTimestamps.append(CACurrentMediaTime()) if frameTimestamps.count > 60 { let fps = calculateFPS() log("PERFORMANCE", "π Current FPS: \(fps)") frameTimestamps.removeFirst() } } }
π Optimization Results:
Users reported camera shutter triggering randomly. The mystery of the ghost clicks!
// Added touch event logging extension UIButton { open override func touchesBegan(_ touches: Set, with event: UIEvent?) { log("TOUCH", "π± Button touched:") log("TOUCH", " - Location: \(touches.first?.location(in: self) ?? .zero)") log("TOUCH", " - Force: \(touches.first?.force ?? 0)") log("TOUCH", " - Type: \(event?.type.rawValue ?? -1)") super.touchesBegan(touches, with: event) } }
Battery life dropping fast in background. Time for some energy profiling!
class EnergyMonitor { static func logEnergyImpact() { let processInfo = ProcessInfo.processInfo log("ENERGY", """ β‘οΈ Energy state: - Thermal state: \(processInfo.thermalState) - CPU usage: \(processInfo.activeProcessorCount) - Memory pressure: \(processInfo.isLowPowerModeEnabled) """) } }
The Culprit Found! π΅οΈββοΈ
iPad users reporting crashes when using split-screen. Down the rabbit hole we go!
extension SceneDelegate { func windowScene(_ scene: UIWindowScene, didUpdateCoordinateSpace coordinateSpace: UICoordinateSpace, interfaceOrientation: UIInterfaceOrientation) { log("SCENE", """ π Scene updated: - Size: \(scene.coordinateSpace.bounds.size) - Scale: \(scene.screen.scale) - Interface: \(interfaceOrientation.rawValue) - Is multitasking: \(scene.screen.isCaptured) """) } }
Chinese characters showing as boxes in the UI. Unicode encoding gone wrong!
extension String { func debugEncoding() { log("STRING", "π€ String analysis:") log("STRING", " - Raw: \(self)") log("STRING", " - UTF8: \(Array(utf8))") log("STRING", " - UTF16: \(Array(utf16))") log("STRING", " - Unicode: \(unicodeScalars.map { $0.value })") } } // Usage "ιθΉ".debugEncoding()
Fixed! π
Time to optimize everything! Our mission: smooth performance on all devices.
π [PERFORMANCE] Final metrics: Initial Launch: - Cold start: 1.2s - Warm start: 0.8s - Memory baseline: 84MB Camera Operations: - Preview start: 892ms - Photo capture: 125ms - Camera flip: 345ms Effect Transitions: - Effect switch: 16ms - Color blend: 8ms - FPS: Locked 60 Memory Profile: - Peak usage: 156MB - Idle usage: 82MB - Leak check: PASS
Production Ready! π
// Our evolved logging system func log(_ category: String, _ message: String, file: String = #file, line: Int = #line) { let timestamp = Date().formatted(date: .omitted, time: .standard) let fileName = (file as NSString).lastPathComponent let emoji = logEmoji(for: category) print("\(emoji) [\(category)] [\(timestamp)] \(message) - \(fileName):\(line)") } // Usage examples: log("CAMERA", "Session configuration started") log("MEMORY", "Current usage: \(currentMemoryUsage)MB") log("UI", "View appeared: \(viewName)")
π Camera freezes after switching effects Steps: 1. Open camera 2. Switch to breathing effect 3. Switch to neon effect quickly 4. App becomes unresponsive
// Strategic log points func switchEffect(_ effect: LightEffect) { log("EFFECT", "β‘οΈ Starting effect switch") log("STATE", "Current memory: \(memoryUsage)MB") log("STATE", "Active threads: \(threadCount)") // ... effect switch code ... log("EFFECT", "β Effect switch completed") }
// Visual debug overlay struct DebugOverlay: ViewModifier { @State private var metrics: [String: Any] = [:] func body(content: Content) -> some View { content.overlay( VStack(alignment: .leading) { Text("FPS: \(metrics["fps"] ?? 0)") Text("Memory: \(metrics["memory"] ?? 0)MB") Text("Active Effects: \(metrics["effects"] ?? 0)") } .font(.caption) .padding() .background(.black.opacity(0.7)) ) } }
Always include context, timestamps, and relevant metrics
Document exact steps to reproduce issues
Use debug overlays for real-time monitoring
Share logs and discuss patterns together
// Info.plist additions <key>NSCameraUsageDescription</key> <string>Camera access is needed for professional lighting and photo capture</string> <key>NSPhotoLibraryUsageDescription</key> <string>Photo access is required to save your captured moments</string>
π± App Store Description: Transform your phone into a professional lighting studio! Key Features: β¨ Professional Light Effects - Breathing effect for dynamic content - Neon glow for creative shots - Starry twinkle for magical moments - Lightning flash for dramatic effects π― Perfect For: - Beauty content creators - Makeup tutorials - Product photography - Live streaming - Video calls π₯ Pro Camera Features: - Split screen lighting - Custom color controls - Real-time effects - Professional camera tools
Clear usage descriptions and privacy policy are crucial
Focus on user benefits rather than technical features
Screenshots should demonstrate the user journey
Metadata in multiple languages increases reach
π§ [App Store Connect] Dear Developer, We are pleased to inform you that your app, Shiba Fill Light, has been approved and will be available on the App Store shortly. Review Notes: β All privacy requirements met β Clear feature presentation β Proper usage descriptions β Compliant with guidelines
This journey article itself is a testament to the power of human-AI collaboration. Using Cursor AI powered by Claude-3.5-sonnet, we:
Transformed weeks of development logs and conversations into a coherent narrative
Maintained precise code examples while making them accessible to readers
Structured the story to be both technically informative and humanly engaging
Be specific about what you want to achieve, but be open to suggestions
Don't expect perfection immediately - build and improve together
Provide context and background - AI can help connect the dots
Combine human creativity with AI's analytical capabilities
This journey shows what's possible when humans and AI work together as partners. Whether you're:
AI is not just a tool - it's a collaborative partner that can help amplify your creativity and technical capabilities.
Professional Lighting Studio in Your Pocket