Swift自定义相机报错:No active and enabled video connection 求助
Hey there! Let's work through why you're hitting this frustrating error when trying to take a photo. I've looked over your code and spotted a few key issues that are causing this problem—let's break them down and fix them step by step.
Key Issues in Your Code
Let's start with what's going wrong:
1. Duplicate stillImageOutput Initialization
In your viewDidAppear method, you create a stillImageOutput, add it to the capture session, but then reinitialize it again at the end of the method. This replaces the output that was actually connected to your session with a brand new, unlinked instance. When you call capturePhoto(with:delegate:), this new output has no active connection to the session—hence the error.
2. No Validation for Active Video Connections
You're not checking if the stillImageOutput has a valid, active video connection before attempting to capture a photo. Even if other setup steps are correct, this can lead to crashes if the camera isn't fully ready.
3. Repeated Capture Session Initialization
You create a new AVCaptureSession every time the view appears. This is unnecessary and can cause conflicts with existing camera connections.
4. Missing Explicit Protocol Conformance
While you have the photoOutput delegate method, your view controller doesn't explicitly state it conforms to AVCapturePhotoCaptureDelegate—this can cause the delegate system to fail silently, breaking the photo capture flow.
Step-by-Step Fixes
Let's fix each issue one by one:
1. Remove the Duplicate stillImageOutput Initialization
In your viewDidAppear method, delete this final line that reinitializes the output:
stillImageOutput = AVCapturePhotoOutput()
2. Add Connection Validation Before Capturing
Update your takePhotoBtnPressed method to check for a valid active connection first—this adds a safety net to prevent crashes:
@IBAction func takePhotoBtnPressed(_ sender: Any) { // Check if we have an active video connection guard let connection = stillImageOutput?.connection(with: .video), connection.isActive else { print("No active video connection available") return } let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg]) stillImageOutput!.capturePhoto(with: settings, delegate: self) }
3. Reuse the Capture Session (Don't Recreate It)
Declare your session, output, and preview layer as class-level properties (not inside methods) so you can reuse them instead of creating new instances every time the view appears:
class YourViewController: UIViewController { @IBOutlet weak var imageView: UIView! @IBOutlet weak var outputImageView: UIImageView! // Class-level properties to persist camera resources private var session: AVCaptureSession? private var stillImageOutput: AVCapturePhotoOutput? private var videoPreviewLayer: AVCaptureVideoPreviewLayer? // Rest of your code... }
Then update viewDidAppear to only initialize the session if it doesn't exist yet:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // Start the session if it already exists, otherwise set up the camera if let session = session { if !session.isRunning { session.startRunning() } } else { setupCamera() } }
4. Explicitly Conform to AVCapturePhotoCaptureDelegate
Update your view controller class declaration to include the protocol—this ensures the system recognizes your delegate methods:
class YourViewController: UIViewController, AVCapturePhotoCaptureDelegate { // Your code here... }
Full Modified Code Example
Here's the complete corrected code with all fixes applied (I've also added a helper method for camera setup to keep things organized):
import UIKit import AVFoundation class CameraViewController: UIViewController, AVCapturePhotoCaptureDelegate { @IBOutlet weak var imageView: UIView! @IBOutlet weak var outputImageView: UIImageView! private var session: AVCaptureSession? private var stillImageOutput: AVCapturePhotoOutput? private var videoPreviewLayer: AVCaptureVideoPreviewLayer? override func viewDidLoad() { super.viewDidLoad() // Handle camera authorization if AVCaptureDevice.authorizationStatus(for: .video) == .authorized { print("Camera authorized") } else { AVCaptureDevice.requestAccess(for: .video) { granted in if granted { DispatchQueue.main.async { // Re-setup camera if permission is granted after initial load self.setupCamera() } } else { print("Camera permission denied") } } } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // Start session if it exists, otherwise setup camera if let session = session { if !session.isRunning { session.startRunning() } } else { setupCamera() } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) session?.stopRunning() } // Helper method to centralize camera setup private func setupCamera() { session = AVCaptureSession() session?.sessionPreset = .low guard let backCamera = AVCaptureDevice.default(for: .video) else { print("Unable to access back camera!") return } do { let input = try AVCaptureDeviceInput(device: backCamera) stillImageOutput = AVCapturePhotoOutput() if let session = session, session.canAddInput(input), session.canAddOutput(stillImageOutput!) { session.addInput(input) session.addOutput(stillImageOutput!) setupLivePreview() } } catch let error { print("Error Unable to initialize back camera: \(error.localizedDescription)") } } @IBAction func takePhotoBtnPressed(_ sender: Any) { guard let connection = stillImageOutput?.connection(with: .video), connection.isActive else { print("No active video connection available") return } let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg]) stillImageOutput!.capturePhoto(with: settings, delegate: self) } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let error = error { print("Photo capture error: \(error.localizedDescription)") return } guard let imageData = photo.fileDataRepresentation() else { print("Failed to get photo data") return } let image = UIImage(data: imageData) outputImageView.image = image } func setupLivePreview() { guard let session = session else { return } videoPreviewLayer = AVCaptureVideoPreviewLayer(session: session) videoPreviewLayer?.videoGravity = .resizeAspectFill videoPreviewLayer?.connection?.videoOrientation = .portrait imageView.layer.addSublayer(videoPreviewLayer!) DispatchQueue.global(qos: .userInitiated).async { session.startRunning() DispatchQueue.main.async { self.videoPreviewLayer?.frame = self.imageView.bounds } } } }
Why These Fixes Work
- Removing duplicate initialization ensures you're using the
stillImageOutputinstance that's actually connected to the capture session. - Connection validation adds a safety check to prevent crashes when the camera isn't fully ready.
- Reusing the session avoids conflicts from creating multiple sessions and ensures consistent camera connections.
- Explicit protocol conformance guarantees the delegate methods are recognized and called correctly by the system.
内容的提问来源于stack exchange,提问作者jonye._.jin

