CameraView icon indicating copy to clipboard operation
CameraView copied to clipboard

[BUG]Crash at second time to present camera view

Open ljhao24 opened this issue 10 months ago • 4 comments

Expected Behavior

Show camera view, close the view, re-open the view.

Current Behavior

Show camera view, close the view, re-open the view but crash.

cameraView.layer.addSublayer(cameraLayer) Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

Code Sample

Just the most simple way to create a MCamera() and .startSession()

Screenshots

Image

Context

Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions.

Name Version
SDK e.g. 3.0.1
Xcode e.g. 16.1
Operating System e.g. iOS 18.2
Device e.g. iPhone 13 Pro Max

ljhao24 avatar Jan 27 '25 10:01 ljhao24

I'm experiencing exactly the same issue.

Usage

struct ImageCaptureView: View {
  @Environment(\.dismiss) private var dismiss

  var body: some View {
    MCamera()
      .onImageCaptured { image, controller in
        controller.closeMCamera()
      }
      .setAudioAvailability(false)
      .setCloseMCameraAction {
        dismiss()
      }
      .startSession()
  }
}

This view is presented using .fullScreenCover(...).

As described by @ljhao24, the camera view works fine when presented for the first time, but crashes as follows when presented a second time:

MijickCamera/CameraManager.swift:77: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

The camera manager's cameraView property is nil.

Context:

  • SDK 3.0.1
  • Xcode 16.2
  • Operating System: iOS 18.3.1
  • Device: iPhone 15 Pro

sepw avatar Mar 04 '25 15:03 sepw

I have invested some time investigating the cause of the crash.

The problem here is that MCamera has a default initializer that creates a new CameraManager instance, along with a new AVCaptureSession for every MCamera instance that is created. This is a problem when using MCamera in a view whose body is evaluated more than once.

I was able to reproduce the crash in an a minimal example app:

  1. Embed the MCamera in a wrapper view, called CameraView
  2. Add an @Environment property to CameraView specifying a closure that is called when an image is captured
import MijickCamera
import SwiftUI

struct CameraView: View {
  @Environment(\.dismiss) private var dismiss
  @Environment(\.imageCaptureAction) private var capture
  
  var body: some View {
    MCamera()
      .onImageCaptured { image, controller in
        guard let data = image.jpegData(compressionQuality: 0.8) else { return }
        capture(data)
        controller.closeMCamera()
      }
      .setCloseMCameraAction {
        dismiss()
      }
      .startSession()
  }
}

extension EnvironmentValues {
  @Entry var imageCaptureAction: (Data) -> Void = { _ in }
}

This view is then presented full screen from the app's ContentView:

struct ContentView: View {
  @State private var isCameraPresented: Bool = false
  
  var body: some View {
    Button {
      isCameraPresented = true
    } label: {
      Image(systemName: "camera.fill")
        .font(.largeTitle)
        .imageScale(.large)
    }
    .fullScreenCover(isPresented: $isCameraPresented) {
      CameraView()
    }
  }
}

CAUSE

The CaptureView body is evaluated multiple times because of the @Environment(\.imageCaptureAction) property. For reasons I am not aware of the addition of this environment property causes the body to be evaluated twice rather than once. This is enough to cause the app to crash when the CameraView is displayed a second time.

The root issue here is the MCamera initializer that, as mentioned above, creates a new CameraManager for every Camera struct instantiated, i.e. every time the containing view's body is evaluated.

WORKAROUND

The only workaround I see for this issue is to ensure that MCamera is only instantiated once for each presentation. This precludes wrapping MCamera in another view: instead use MCamera directly within the fullScreenCover(...) closure that is presenting it.

sepw avatar Mar 05 '25 10:03 sepw

I discovered that If it's wrapped in a NavigationStack it won't crash, but it won't stop the session upon dismiss.

alexeyndru avatar Sep 04 '25 16:09 alexeyndru

I patched the ObservableObject that holds the CameraManager and converted it to StateObject, and since then I haven't seen any crashes.

theoks avatar Sep 26 '25 19:09 theoks