Camera works, provided you don't go back too rapidly

This commit is contained in:
2026-02-15 14:15:34 -06:00
parent 45035e67e0
commit 55b48575cc
5 changed files with 80 additions and 11 deletions

View File

@@ -26,9 +26,11 @@ final class CameraManager: NSObject, ObservableObject {
private let continuationQueue = DispatchQueue(label: "com.souschef.continuation") private let continuationQueue = DispatchQueue(label: "com.souschef.continuation")
private var isConfigured = false private var isConfigured = false
private var cachedPreviewLayer: AVCaptureVideoPreviewLayer?
nonisolated override init() { nonisolated override init() {
super.init() super.init()
print("🎥 CameraManager.init() - Instance created at \(Date())")
} }
// MARK: - Authorization // MARK: - Authorization
@@ -53,8 +55,12 @@ final class CameraManager: NSObject, ObservableObject {
// MARK: - Session Setup // MARK: - Session Setup
func setupSession() async throws { func setupSession() async throws {
print("🎥 CameraManager.setupSession() - STARTED at \(Date())")
// Only configure once // Only configure once
guard !isConfigured else { return } guard !isConfigured else {
print("🎥 CameraManager.setupSession() - Already configured, returning")
return
}
// Ensure authorization is checked first // Ensure authorization is checked first
await checkAuthorization() await checkAuthorization()
@@ -63,19 +69,29 @@ final class CameraManager: NSObject, ObservableObject {
throw CameraError.notAuthorized throw CameraError.notAuthorized
} }
print("🎥 CameraManager.setupSession() - Calling beginConfiguration()")
captureSession.beginConfiguration() captureSession.beginConfiguration()
// Set session preset // Set session preset
captureSession.sessionPreset = .high captureSession.sessionPreset = .high
print("🎥 CameraManager.setupSession() - Set preset to .high")
// Add video input // Add video input
print("🎥 CameraManager.setupSession() - About to get video device (LINE 72)")
#if targetEnvironment(simulator)
print("🎥 CameraManager.setupSession() - ⚠️ RUNNING ON SIMULATOR - Camera may not work properly")
#endif
guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back), guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
let videoInput = try? AVCaptureDeviceInput(device: videoDevice), let videoInput = try? AVCaptureDeviceInput(device: videoDevice),
captureSession.canAddInput(videoInput) else { captureSession.canAddInput(videoInput) else {
print("🎥 CameraManager.setupSession() - ❌ FAILED to get video device or add input")
captureSession.commitConfiguration() captureSession.commitConfiguration()
throw CameraError.setupFailed throw CameraError.setupFailed
} }
print("🎥 CameraManager.setupSession() - ✅ Successfully got video device and input")
captureSession.addInput(videoInput) captureSession.addInput(videoInput)
// Add video output // Add video output
@@ -96,6 +112,7 @@ final class CameraManager: NSObject, ObservableObject {
captureSession.commitConfiguration() captureSession.commitConfiguration()
isConfigured = true isConfigured = true
print("🎥 CameraManager.setupSession() - ✅ COMPLETED successfully at \(Date())")
} }
// MARK: - Session Control // MARK: - Session Control
@@ -151,9 +168,26 @@ final class CameraManager: NSObject, ObservableObject {
// MARK: - Preview Layer // MARK: - Preview Layer
func previewLayer() -> AVCaptureVideoPreviewLayer { func previewLayer() -> AVCaptureVideoPreviewLayer? {
print("🎥 CameraManager.previewLayer() - ⚠️ CALLED at \(Date()) - isConfigured: \(isConfigured)")
// Only create preview layer after session is configured
if !isConfigured {
print("🎥 CameraManager.previewLayer() - ❌ Session not configured yet, returning nil")
return nil
}
// Return cached layer if available
if let cached = cachedPreviewLayer {
print("🎥 CameraManager.previewLayer() - ✅ Returning cached preview layer")
return cached
}
// Create and cache new preview layer
print("🎥 CameraManager.previewLayer() - ✅ Creating new preview layer")
let layer = AVCaptureVideoPreviewLayer(session: captureSession) let layer = AVCaptureVideoPreviewLayer(session: captureSession)
layer.videoGravity = .resizeAspectFill layer.videoGravity = .resizeAspectFill
cachedPreviewLayer = layer
return layer return layer
} }
} }

View File

@@ -71,7 +71,7 @@ final class CookingModeViewModel: ObservableObject {
cameraManager.stopSession() cameraManager.stopSession()
} }
func getPreviewLayer() -> AVCaptureVideoPreviewLayer { func getPreviewLayer() -> AVCaptureVideoPreviewLayer? {
cameraManager.previewLayer() cameraManager.previewLayer()
} }

View File

@@ -25,6 +25,7 @@ final class ScannerViewModel: ObservableObject {
nonisolated init(visionService: VisionService = ARVisionService(), nonisolated init(visionService: VisionService = ARVisionService(),
cameraManager: CameraManager = CameraManager()) { cameraManager: CameraManager = CameraManager()) {
print("📱 ScannerViewModel.init() - Creating ViewModel at \(Date())")
self.visionService = visionService self.visionService = visionService
self.cameraManager = cameraManager self.cameraManager = cameraManager
} }
@@ -32,9 +33,12 @@ final class ScannerViewModel: ObservableObject {
// MARK: - Camera Management // MARK: - Camera Management
func setupCamera() async { func setupCamera() async {
print("📱 ScannerViewModel.setupCamera() - STARTED at \(Date())")
do { do {
try await cameraManager.setupSession() try await cameraManager.setupSession()
print("📱 ScannerViewModel.setupCamera() - ✅ SUCCESS at \(Date())")
} catch { } catch {
print("📱 ScannerViewModel.setupCamera() - ❌ ERROR: \(error)")
self.error = error self.error = error
} }
} }
@@ -47,8 +51,9 @@ final class ScannerViewModel: ObservableObject {
cameraManager.stopSession() cameraManager.stopSession()
} }
func getPreviewLayer() -> AVCaptureVideoPreviewLayer { func getPreviewLayer() -> AVCaptureVideoPreviewLayer? {
cameraManager.previewLayer() print("📱 ScannerViewModel.getPreviewLayer() - ⚠️ REQUESTING preview layer at \(Date())")
return cameraManager.previewLayer()
} }
// MARK: - Scanning // MARK: - Scanning

View File

@@ -12,6 +12,7 @@ struct CookingModeView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: CookingModeViewModel @StateObject private var viewModel: CookingModeViewModel
@State private var showingAllSteps = false @State private var showingAllSteps = false
@State private var previewLayer: AVCaptureVideoPreviewLayer?
init(recipe: Recipe) { init(recipe: Recipe) {
_viewModel = StateObject(wrappedValue: CookingModeViewModel(recipe: recipe)) _viewModel = StateObject(wrappedValue: CookingModeViewModel(recipe: recipe))
@@ -21,8 +22,8 @@ struct CookingModeView: View {
NavigationStack { NavigationStack {
ZStack { ZStack {
// Camera preview background // Camera preview background
if viewModel.isMonitoring { if viewModel.isMonitoring, let previewLayer = previewLayer {
CameraPreviewView(previewLayer: viewModel.getPreviewLayer()) CameraPreviewView(previewLayer: previewLayer)
.ignoresSafeArea() .ignoresSafeArea()
.opacity(0.3) .opacity(0.3)
} }
@@ -69,6 +70,7 @@ struct CookingModeView: View {
} }
.task { .task {
await viewModel.setupCamera() await viewModel.setupCamera()
previewLayer = viewModel.getPreviewLayer()
viewModel.startCamera() viewModel.startCamera()
} }
.onDisappear { .onDisappear {

View File

@@ -16,9 +16,15 @@ struct ScannerView: View {
@State private var detectedPlanes = 0 @State private var detectedPlanes = 0
@State private var lastRaycastResult = "" @State private var lastRaycastResult = ""
@State private var showARView = false @State private var showARView = false
@State private var previewLayer: AVCaptureVideoPreviewLayer?
init() {
print("🔵 ScannerView.init() - View initialized at \(Date())")
}
var body: some View { var body: some View {
NavigationStack { print("🔵 ScannerView.body - Body evaluated at \(Date()), showARView: \(showARView)")
return NavigationStack {
ZStack { ZStack {
// AR camera preview or regular camera // AR camera preview or regular camera
if showARView { if showARView {
@@ -28,8 +34,17 @@ struct ScannerView: View {
) )
.ignoresSafeArea() .ignoresSafeArea()
} else { } else {
CameraPreviewView(previewLayer: viewModel.getPreviewLayer()) if let previewLayer = previewLayer {
.ignoresSafeArea() CameraPreviewView(previewLayer: previewLayer)
.ignoresSafeArea()
} else {
Color.black
.ignoresSafeArea()
.overlay {
ProgressView("Initializing camera...")
.foregroundStyle(.white)
}
}
} }
// Overlay UI // Overlay UI
@@ -68,12 +83,19 @@ struct ScannerView: View {
} }
} }
.task { .task {
print("🔵 ScannerView.task - Task started at \(Date())")
if !showARView { if !showARView {
print("🔵 ScannerView.task - Calling setupCamera()")
await viewModel.setupCamera() await viewModel.setupCamera()
print("🔵 ScannerView.task - Getting preview layer after setup")
previewLayer = viewModel.getPreviewLayer()
print("🔵 ScannerView.task - Preview layer set: \(previewLayer != nil)")
print("🔵 ScannerView.task - Calling startCamera()")
viewModel.startCamera() viewModel.startCamera()
} }
} }
.onDisappear { .onDisappear {
print("🔵 ScannerView.onDisappear - Cleaning up at \(Date())")
viewModel.cleanup() viewModel.cleanup()
} }
.alert("Camera Error", isPresented: .constant(viewModel.error != nil)) { .alert("Camera Error", isPresented: .constant(viewModel.error != nil)) {
@@ -174,7 +196,11 @@ struct ScannerView: View {
withAnimation { withAnimation {
showARView.toggle() showARView.toggle()
if !showARView { if !showARView {
viewModel.startCamera() Task {
await viewModel.setupCamera()
previewLayer = viewModel.getPreviewLayer()
viewModel.startCamera()
}
} else { } else {
viewModel.stopCamera() viewModel.stopCamera()
} }
@@ -246,10 +272,12 @@ struct CameraPreviewView: UIViewRepresentable {
let previewLayer: AVCaptureVideoPreviewLayer let previewLayer: AVCaptureVideoPreviewLayer
func makeUIView(context: Context) -> UIView { func makeUIView(context: Context) -> UIView {
print("🟢 CameraPreviewView.makeUIView() - Creating preview view at \(Date())")
let view = UIView(frame: .zero) let view = UIView(frame: .zero)
view.backgroundColor = .black view.backgroundColor = .black
previewLayer.frame = view.bounds previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer) view.layer.addSublayer(previewLayer)
print("🟢 CameraPreviewView.makeUIView() - Preview layer added to view")
return view return view
} }