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