import AVFoundation import SwiftUI /// Camera-based QR scanner for pairing codes. struct QRScannerView: UIViewControllerRepresentable { let onScan: (String) -> Void func makeUIViewController(context: Context) -> ScannerViewController { let controller = ScannerViewController() controller.onScan = onScan return controller } func updateUIViewController(_ controller: ScannerViewController, context: Context) {} } final class ScannerViewController: UIViewController, @preconcurrency AVCaptureMetadataOutputObjectsDelegate { var onScan: ((String) -> Void)? private let session = AVCaptureSession() private var previewLayer: AVCaptureVideoPreviewLayer? private var didScan = false override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .black guard let device = AVCaptureDevice.default(for: .video), let input = try? AVCaptureDeviceInput(device: device), session.canAddInput(input) else { showUnavailableLabel() return } session.addInput(input) let output = AVCaptureMetadataOutput() guard session.canAddOutput(output) else { showUnavailableLabel() return } session.addOutput(output) output.setMetadataObjectsDelegate(self, queue: .main) output.metadataObjectTypes = [.qr] let preview = AVCaptureVideoPreviewLayer(session: session) preview.videoGravity = .resizeAspectFill preview.frame = view.bounds view.layer.addSublayer(preview) previewLayer = preview } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() previewLayer?.frame = view.bounds } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) didScan = false let session = session DispatchQueue.global(qos: .userInitiated).async { if !session.isRunning { session.startRunning() } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) let session = session DispatchQueue.global(qos: .userInitiated).async { if session.isRunning { session.stopRunning() } } } func metadataOutput( _ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection ) { guard !didScan, let object = metadataObjects.first as? AVMetadataMachineReadableCodeObject, object.type == .qr, let value = object.stringValue else { return } didScan = true UINotificationFeedbackGenerator().notificationOccurred(.success) onScan?(value) } private func showUnavailableLabel() { let label = UILabel() label.text = "Camera unavailable" label.textColor = .white label.textAlignment = .center label.frame = view.bounds label.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(label) } }