πŸ• From Simple Light to Pro Camera: The Shiba Fill Light Journey

The Spark ✨

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.

The Blueprint: Our First Design Session πŸ“

"I want to build a beautiful and easy to use Fill Light App for iOS..."
"Let's break this down into a comprehensive design specification. We'll need to consider the architecture, core features, and user experience."

Initial Feature Brainstorming

# 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
            

Key Technical Decisions:

  • MVVM Architecture

    Chosen for clean separation of UI and business logic, perfect for SwiftUI

  • Three-Layer Design
    ZStack {
        LightBackgroundLayer()  // Color effects
        CameraPreviewLayer()    // Optional camera
        ControlPanelLayer()     // UI controls
    }
                            
"How should we handle the light effects and camera integration?"
"Let's create dedicated services for each major feature: - LightEffectService for effects - CameraService for camera handling - HapticManager for feedback This will keep the code modular and maintainable."

UI/UX Planning

πŸ“± Main Screen Layout:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    Light Panel   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Camera Preview  β”‚ ← Draggable
β”‚       ⭕️        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Control Panel    β”‚ ← Collapsible
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                
"The key is making it simple enough for casual users but powerful enough for professionals."

Project Architecture & Organization πŸ—οΈ

Project Structure

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
        

The Architecture Journey πŸ—οΈ

"At first, I just put everything in ContentView.swift..."
"Let's break this down into manageable pieces. Think of it like building blocks - each piece should do one thing well."

Step 1: Breaking Down the Monolith

// 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)
    }
}
                
"But now I need to share the camera state between views..."
"Perfect use case for a singleton service! Let's create CameraService as a shared instance."

Step 2: Service Layer Design

// 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
}
                
"Each service is like a specialized expert - it does one job and does it well."

Step 3: View Organization

"The views are getting complex with all these gestures and effects..."
"Let's create custom ViewModifiers for reusable behaviors. Think of them as view plugins!"
// 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))
                

Key Architecture Insights:

  • Single Responsibility:

    Each component should have one clear purpose - made debugging much easier!

  • State Management:

    Using ObservableObject for services and @Published for reactive updates was a game-changer

  • View Composition:

    Breaking down complex views into smaller, reusable components improved maintainability

  • Service Layer:

    Singletons for services ensured consistent state across the app

Key Development Practices

The AI Partnership πŸ€–

Working with Cursor AI and Claude-3.5-sonnet transformed our development process:

The Evolution

Our journey had three major breakthroughs:

1. Basic Light Panel β†’ Professional Tool

// Day 1: Just a color picker
Rectangle()
    .fill(currentColor)
    .ignoresSafeArea()

// Today: Professional lighting system
ZStack {
    LightEffectLayer(effect: currentEffect)
    CameraPreviewLayer()
    ControlPanel()
}
            

2. The Camera Challenge

The game-changer moment came at 2 AM while debugging. Camera startup: 9 seconds β†’ 1 second!

"Sometimes the biggest breakthroughs come from the most frustrating challenges."

3. Light Effects Magic

Added professional effects that creators love:

πŸ“ Development Diary: The Real Story

Day 1: The Foundation

November 29, 2024

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
        }
    }
}
                

Day 3: The First Crisis 😱

December 1, 2024

Added camera integration - everything broke. Camera preview upside down, app freezing.

"This is where Cursor AI saved us. Instead of digging through git history, we could quickly prototype and test solutions."

Backup Strategy Born:

  • πŸ“¦ Zip entire project before major changes
  • πŸ’Ύ Keep working versions as restoration points
  • πŸ”„ Use Cursor for rapid prototyping

Day 5: The 2 AM Breakthrough

December 3, 2024

Camera initialization taking 9 seconds. Multiple backup restores as we tried different approaches.

"Everything is broken again. Should we revert?"
"Wait - I see the issue. The camera session queue isn't properly configured..."

πŸŽ‰ Finally fixed: Camera startup reduced to 1 second!

Day 7: The UI Revolution

December 5, 2024

Major UI overhaul - another "zip backup" moment. Added draggable camera preview and effect controls.

Why Traditional Git Wasn't Enough:

  • Multiple experimental branches became confusing
  • Rapid UI iterations needed quick rollbacks
  • Some changes affected multiple files simultaneously

Day 8: The Split Screen Saga 🎭

December 6, 2024

Implementing split-screen mode seemed simple... until it wasn't.

"The split view works, but the colors are mixing wrong at the border"
"Let's try using a mask layer instead of two separate views..."
// 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"
        }
    }
}
                

Day 10: The Great Camera Flip Mystery πŸ”„

December 8, 2024

Front camera worked perfectly. Back camera? Upside down!

The Symptoms:

  • Back camera preview flipped 180Β°
  • Photos saving in wrong orientation
  • UI controls disappearing on flip

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
}
                

Day 12: Light Effect Breakthrough πŸ’‘

December 10, 2024

Adding dynamic light effects was like choreographing a light show.

The Challenge:

How to create smooth, professional-looking effects without draining the battery?

  1. Created effect preview system
  2. Implemented intensity controls
  3. Added speed adjustment
  4. Optimized performance
"The breathing effect looks amazing! How did you make it so smooth?"

Day 9: The Debug Log Revolution πŸ”

December 7, 2024

Camera performance was inconsistent across devices. Time to get serious about logging.

Initial Debug Logs

// 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)")
                
"Here's the debug log showing the camera initialization steps..."
"I see the issue - there's a 3-second delay between configuration and commitment. Let's add timestamps..."

Day 11: The iPad Layout Crisis πŸ“±

December 9, 2024

Everything looked perfect on iPhone. Then we tested on iPad...

iPad Issues:

  • Controls floating in wrong positions
  • Camera preview too small
  • Split mode not respecting safe areas

The Solution: Adaptive Layout

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)")
                }
            }
        )
    }
}
                

Day 13: The Memory Mystery 🧩

December 11, 2024

The app started crashing after extended camera use. Time for some memory debugging.

Memory Trace Logs:

// 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 usage keeps climbing after each camera switch..."
"Let's analyze the session cleanup. Ah - we're not removing old inputs and outputs!"

πŸŽ‰ Memory leak fixed! Usage now stable at ~100MB

Day 14: App Store Submission Drama

December 12, 2024

First submission rejected! Issues with:

"Sometimes the hardest part isn't building the feature - it's getting it through review!"

Day 15: The Mysterious Camera Freeze πŸ₯Ά

December 13, 2024

Users reported random camera freezes when switching effects. Time for some serious debugging!

The Investigation

// 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")
}
                
"Here's the log from a freeze - notice the timing between effect changes..."
"The effects are switching faster than the render cycle. We need to add a debounce!"
// 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
        }
    }
}
                    

Day 16: The Case of the Disappearing Controls πŸ‘»

December 14, 2024

Control panel randomly disappearing on device rotation. Another UI mystery to solve!

Debug Strategy

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)")
            }
    }
}
                
"The controls disappear exactly when rotating to landscape..."
"The frame calculation is happening before rotation completes. Let's add a rotation completion handler."

Day 17: The Light Effect Performance Quest ⚑️

December 15, 2024

Light effects causing frame drops on older devices. Time to optimize!

Performance Profiling

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()
        }
    }
}
                
"The breathing effect drops to 30fps on iPhone X..."
"Let's optimize the animation using CADisplayLink instead of Timer."

πŸŽ‰ Optimization Results:

  • FPS stable at 60
  • Battery usage reduced by 30%
  • Smooth transitions on all devices

Day 18: The Phantom Button Press πŸ‘»

December 16, 2024

Users reported camera shutter triggering randomly. The mystery of the ghost clicks!

The Investigation

// 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)
    }
}
                
"Look at this log - phantom touches at exact 5-second intervals!"
"Ah! The auto-focus timer is sending synthetic touch events. We need to filter those."

Day 19: The Great Battery Drain Mystery πŸ”‹

December 17, 2024

Battery life dropping fast in background. Time for some energy profiling!

Energy Impact Logging

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! πŸ•΅οΈβ€β™‚οΈ

  • Timer not invalidated in background
  • Camera session still running
  • Effect renderer not paused

Day 20: The Multi-tasking Mayhem πŸ“±

December 18, 2024

iPad users reporting crashes when using split-screen. Down the rabbit hole we go!

Scene Management Debug

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)
            """)
    }
}
                
"The camera preview gets stuck when entering split view..."
"We need to handle screen capture state changes. Let's rebuild the camera session on bounds change."

Day 21: The Localization Adventure 🌍

December 19, 2024

Chinese characters showing as boxes in the UI. Unicode encoding gone wrong!

String Debugging

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! πŸŽ‰

  • Proper UTF-8 encoding in strings file
  • Font fallback handling
  • Right-to-left support added

Day 22: The Great Performance Hunt 🎯

December 20, 2024

Time to optimize everything! Our mission: smooth performance on all devices.

Performance Audit Results

πŸ“Š [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! πŸš€

  • All performance targets met
  • Memory usage optimized
  • Battery efficiency maximized
  • UI responsiveness perfected

Debug Tips & Collaboration 🀝

How We Solved Issues Together

1. Structured Logging Strategy

// 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)")
                    
"Here's the camera initialization log..."
"I see a pattern - the delay always happens after the third configuration attempt. Let's add timestamps between steps."

2. Collaborative Debug Process

  1. User Reports Issue:
    πŸ› Camera freezes after switching effects
    Steps:
    1. Open camera
    2. Switch to breathing effect
    3. Switch to neon effect quickly
    4. App becomes unresponsive
                                    
  2. Add Debug Points:
    // 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")
    }
                                    
  3. Share Logs:
    "Here's the full log trace..."
    "Let's analyze this step by step. First, notice the thread count increasing..."

3. Debug Visualization Techniques

// 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))
        )
    }
}
                    

Key Debug Tips:

  • Comprehensive Logging:

    Always include context, timestamps, and relevant metrics

  • Reproducible Steps:

    Document exact steps to reproduce issues

  • Visual Feedback:

    Use debug overlays for real-time monitoring

  • Collaborative Analysis:

    Share logs and discuss patterns together

The App Store Journey & Marketing πŸš€

Preparing for App Review

"Our first submission was rejected. They mentioned privacy concerns..."
"Let's go through the rejection points systematically: 1. Privacy Policy needed 2. Camera usage description missing 3. Photo library access justification required"

App Store Requirements Fixed:

// 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>
                

Marketing Material Development

"How should we present the app's features in the store?"
"Let's structure it around key user benefits. Here's a draft:"
πŸ“± 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
                    

App Store Screenshots

"Which features should we highlight in screenshots?"
"Let's tell a visual story: 1. Main light panel with effects 2. Split screen before/after 3. Camera integration 4. Professional controls 5. Custom color picker"

Key App Store Insights:

  • Privacy First:

    Clear usage descriptions and privacy policy are crucial

  • Feature Presentation:

    Focus on user benefits rather than technical features

  • Visual Story:

    Screenshots should demonstrate the user journey

  • Localization:

    Metadata in multiple languages increases reach

Final Submission Success! πŸŽ‰

πŸ“§ [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
                

Building This Story Together: Human + AI 🀝

This journey article itself is a testament to the power of human-AI collaboration. Using Cursor AI powered by Claude-3.5-sonnet, we:

  • Organized Complex Information

    Transformed weeks of development logs and conversations into a coherent narrative

  • Preserved Technical Accuracy

    Maintained precise code examples while making them accessible to readers

  • Created Engaging Content

    Structured the story to be both technically informative and humanly engaging

"How should we structure this development story?"
"Let's break it into key moments - from simple beginnings to complex challenges. Each problem we solved is a lesson for others."

Tips for Effective AI Collaboration:

To Future Developers πŸš€

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.

"This journey taught us something beautiful: when human creativity meets AI capabilities, magic happens. Here's to capturing more magical moments together! πŸ“Έβœ¨"

Try Shiba Fill Light Today! ✨

Shiba Fill Light

Professional Lighting Studio in Your Pocket

  • ✨ Professional Light Effects
  • πŸ“Έ Advanced Camera Controls
  • 🎨 Custom Color Options
  • 🌟 Creative Light Modes
Size 1.4 MB
Languages 11 languages supported
Requirements iOS 16.0 or later
Download on the App Store