import ZXingObjC import AVFoundation let BARCODE_TYPES_KEY = "barcodeTypes" class BarcodeScanner: NSObject, BarcodeScanningResponseHandler { private var onBarcodeScanned: (([String: Any]?) -> Void)? var isScanningBarcodes = false // MARK: - Properties private let session: AVCaptureSession private let sessionQueue: DispatchQueue private let zxingCaptureQueue = DispatchQueue(label: "com.zxing.captureQueue") private var metadataOutput: AVCaptureMetadataOutput? private var videoDataOutput: AVCaptureVideoDataOutput? private var settings = BarcodeScannerUtils.getDefaultSettings() private var zxingBarcodeReaders: [AVMetadataObject.ObjectType: ZXReader] = [ AVMetadataObject.ObjectType.pdf417: ZXPDF417Reader(), AVMetadataObject.ObjectType.code39: ZXCode39Reader() ] private var previewLayer: AVCaptureVideoPreviewLayer? private var zxingEnabled = true private var delegate: MetaDataDelegate? init(session: AVCaptureSession, sessionQueue: DispatchQueue) { self.session = session self.sessionQueue = sessionQueue if #available(iOS 15.4, *) { zxingBarcodeReaders[AVMetadataObject.ObjectType.codabar] = ZXCodaBarReader() } } func setSettings(_ newSettings: [String: [AVMetadataObject.ObjectType]]) { for (key, value) in newSettings where key == BARCODE_TYPES_KEY { let previousTypes = Set(settings[BARCODE_TYPES_KEY] ?? []) let newTypes = Set(value) if previousTypes != newTypes { settings[BARCODE_TYPES_KEY] = value let zxingCoveredTypes = Set(zxingBarcodeReaders.keys) zxingEnabled = !zxingCoveredTypes.isDisjoint(with: newTypes) maybeStartBarcodeScanning() } } } func setPreviewLayer(layer: AVCaptureVideoPreviewLayer) { self.previewLayer = layer } func setIsEnabled(_ enabled: Bool) { guard isScanningBarcodes != enabled else { return } isScanningBarcodes = enabled if isScanningBarcodes { if metadataOutput != nil { setConnection(enabled: true) } else { maybeStartBarcodeScanning() } } else { setConnection(enabled: false) stopBarcodeScanning() } } func setConnection(enabled: Bool) { metadataOutput?.connections.forEach { $0.isEnabled = enabled } } func setOnBarcodeScanned(_ onBarcodeScanned: @escaping ([String: Any]?) -> Void) { self.onBarcodeScanned = onBarcodeScanned } func maybeStartBarcodeScanning() { guard isScanningBarcodes else { return } if metadataOutput == nil || videoDataOutput == nil { addOutputs() if metadataOutput == nil { return } } let availableObjectTypes: [AVMetadataObject.ObjectType] = metadataOutput?.availableMetadataObjectTypes ?? [] let requestedTypes = (settings[BARCODE_TYPES_KEY] ?? []).filter { availableObjectTypes.contains($0) } metadataOutput?.metadataObjectTypes = requestedTypes } func stopBarcodeScanning() { removeOutputs() if isScanningBarcodes { onBarcodeScanned?(nil) } } private func addOutputs() { delegate = MetaDataDelegate( settings: settings, previewLayer: previewLayer, zxingBarcodeReaders: zxingBarcodeReaders, zxingEnabled: zxingEnabled, metadataResultHandler: self) session.beginConfiguration() if metadataOutput == nil { let output = AVCaptureMetadataOutput() output.setMetadataObjectsDelegate(delegate, queue: sessionQueue) if session.canAddOutput(output) { session.addOutput(output) metadataOutput = output } } if videoDataOutput == nil { let output = AVCaptureVideoDataOutput() output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA] output.alwaysDiscardsLateVideoFrames = true output.setSampleBufferDelegate(delegate, queue: zxingCaptureQueue) if session.canAddOutput(output) { session.addOutput(output) videoDataOutput = output } } session.commitConfiguration() } private func removeOutputs() { session.beginConfiguration() defer { session.commitConfiguration() } if let metadataOutput { if session.outputs.contains(metadataOutput) { session.removeOutput(metadataOutput) self.metadataOutput = nil } } if let videoDataOutput { if session.outputs.contains(videoDataOutput) { session.removeOutput(videoDataOutput) self.videoDataOutput = nil } } } func onScanningResult(_ result: [String: Any]) { self.onBarcodeScanned?(result) } }