ARTICLE AD BOX
The reason the coordinator is not triggered is that adding the output in updateUIViewController works, but you're missing beginConfiguration/commitConfiguration around the session mutation.
Without those, AVFoundation may silently reject the change on a running session.
The fix is:
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { captureSession.beginConfiguration() if getScan { if captureSession.canAddOutput(videoOutput) { videoOutput.setSampleBufferDelegate(context.coordinator, queue: DispatchQueue(label: "videoQueue")) captureSession.addOutput(videoOutput) } } else { captureSession.removeOutput(videoOutput) } captureSession.commitConfiguration() }beginConfiguration() — the beginConfiguration and commitConfiguration methods ensure that device changes occur as a group, minimizing visibility or inconsistency of state. After calling beginConfiguration, you can add or remove outputs, alter the sessionPreset property, or configure individual capture input or output properties. No changes are actually made until you invoke commitConfiguration, at which time they are applied together. https://developer.apple.com/documentation/avfoundation/avcapturesession/1389174-beginconfiguration
commitConfiguration() — commits one or more changes to a running capture session's configuration in a single atomic update. https://developer.apple.com/documentation/avfoundation/avcapturesession/1388173-commitconfiguration
But with that said, repeatedly adding and removing outputs from a live session is fragile — which is why a guard approach in the coordinator would be simpler and more reliable.
So despite the earlier approach is valid in concept, adding/removing outputs from a running AVCaptureSession requires wrapping in beginConfiguration/commitConfiguration, and doing it in updateUIViewController can be called multiple times unexpectedly.
The simpler approach is to always keep the output attached and just have the coordinator ignore frames when getScan is false:
class Coordinator: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { var parent: ScannerView init(_ parent: ScannerView) { self.parent = parent } func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // ignore frames unless a scan was requested guard parent.getScan else { return } guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } let request = VNDetectBarcodesRequest { request, error in guard let results = request.results as? [VNBarcodeObservation], let barcode = results.first?.payloadStringValue else { return } DispatchQueue.main.async { self.parent.scannedString = barcode self.parent.getScan = false // stop after first result } } try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request]) } }And in makeUIViewController always add the output unconditionally — remove updateUIViewController entirely or leave it empty:
if captureSession.canAddOutput(videoOutput) { videoOutput.setSampleBufferDelegate(context.coordinator, queue: DispatchQueue(label: "videoQueue")) captureSession.addOutput(videoOutput) }This way the camera pipeline is never interrupted, the preview stays live, and the coordinator simply does nothing until getScan is true.
When a barcode is found it sets getScan back to false, which causes the guard to block further processing until the button is pressed again.
