// Native iOS QR capture for the join page — AVCaptureMetadataOutput does QR // detection in hardware-adjacent fashion on iOS (no Vision pass needed, unlike // the macOS scanner). Emits every decoded payload; the caller decides what to // do (and when to dismiss). import SwiftUI import AVFoundation struct QRScanView: UIViewControllerRepresentable { let onCode: (String) -> Void func makeUIViewController(context: Context) -> ScannerController { let vc = ScannerController() vc.onCode = onCode return vc } func updateUIViewController(_ uiViewController: ScannerController, context: Context) {} final class ScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { var onCode: ((String) -> Void)? private let session = AVCaptureSession() private let sessionQueue = DispatchQueue(label: "tva.qr-scan") override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .black let preview = AVCaptureVideoPreviewLayer(session: session) preview.videoGravity = .resizeAspectFill preview.frame = view.bounds view.layer.addSublayer(preview) AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in guard granted, let self else { return } self.sessionQueue.async { self.configure() } } } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() view.layer.sublayers?.first?.frame = view.bounds } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) sessionQueue.async { [session] in if session.isRunning { session.stopRunning() } } } private func configure() { guard session.inputs.isEmpty, let camera = AVCaptureDevice.default(for: .video), let input = try? AVCaptureDeviceInput(device: camera), session.canAddInput(input) else { return } session.addInput(input) let output = AVCaptureMetadataOutput() guard session.canAddOutput(output) else { return } session.addOutput(output) output.setMetadataObjectsDelegate(self, queue: .main) output.metadataObjectTypes = [.qr] session.startRunning() } func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let payload = (metadataObjects.first as? AVMetadataMachineReadableCodeObject)? .stringValue else { return } onCode?(payload) } } }