import AVFoundation import SwiftUI class BarcodeScanner: NSObject, ObservableObject { @Published var scannedCode: String? @Published var isScanning = false @Published var error: String? private var captureSession: AVCaptureSession? private let metadataOutput = AVCaptureMetadataOutput() // Supported barcode types for food products private let supportedTypes: [AVMetadataObject.ObjectType] = [ .ean8, .ean13, .upce, .code128, .code39, .code93, .itf14 ] override init() { super.init() } // MARK: - Public Interface func checkPermission() async -> Bool { switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: return true case .notDetermined: return await AVCaptureDevice.requestAccess(for: .video) default: return false } } func setupSession() { guard captureSession == nil else { return } let session = AVCaptureSession() session.sessionPreset = .high guard let videoDevice = AVCaptureDevice.default(for: .video), let videoInput = try? AVCaptureDeviceInput(device: videoDevice) else { error = "Camera not available" return } if session.canAddInput(videoInput) { session.addInput(videoInput) } if session.canAddOutput(metadataOutput) { session.addOutput(metadataOutput) metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) metadataOutput.metadataObjectTypes = supportedTypes.filter { metadataOutput.availableMetadataObjectTypes.contains($0) } } captureSession = session } func startScanning() { guard let session = captureSession else { setupSession() startScanning() return } scannedCode = nil error = nil DispatchQueue.global(qos: .userInitiated).async { [weak self] in session.startRunning() DispatchQueue.main.async { self?.isScanning = true } } } func stopScanning() { captureSession?.stopRunning() isScanning = false } func reset() { scannedCode = nil error = nil } var previewLayer: AVCaptureVideoPreviewLayer? { guard let session = captureSession else { return nil } let layer = AVCaptureVideoPreviewLayer(session: session) layer.videoGravity = .resizeAspectFill return layer } } // MARK: - AVCaptureMetadataOutputObjectsDelegate extension BarcodeScanner: AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject, let code = metadataObject.stringValue, scannedCode == nil else { return } // Haptic feedback let generator = UIImpactFeedbackGenerator(style: .medium) generator.impactOccurred() scannedCode = code stopScanning() } } // MARK: - Camera Preview View struct CameraPreviewView: UIViewRepresentable { let scanner: BarcodeScanner func makeUIView(context: Context) -> UIView { let view = UIView(frame: .zero) view.backgroundColor = .black if let previewLayer = scanner.previewLayer { previewLayer.frame = view.bounds view.layer.addSublayer(previewLayer) } return view } func updateUIView(_ uiView: UIView, context: Context) { DispatchQueue.main.async { if let sublayer = uiView.layer.sublayers?.first as? AVCaptureVideoPreviewLayer { sublayer.frame = uiView.bounds } else if let previewLayer = scanner.previewLayer { previewLayer.frame = uiView.bounds uiView.layer.addSublayer(previewLayer) } } } }