import SwiftUI import AVFoundation struct QRScannerSheet: View { var onResult: (String) -> Void @Environment(\.dismiss) private var dismiss var body: some View { NavigationStack { QRCameraView(onResult: { result in dismiss() onResult(result) }) .ignoresSafeArea() .navigationTitle("Scan QR Code") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Cancel") { dismiss() } .foregroundStyle(.white) } } } } } private struct QRCameraView: UIViewRepresentable { var onResult: (String) -> Void func makeUIView(context: Context) -> UIView { let view = UIView() view.backgroundColor = .black let session = AVCaptureSession() guard let device = AVCaptureDevice.default(for: .video), let input = try? AVCaptureDeviceInput(device: device) else { return view } if session.canAddInput(input) { session.addInput(input) } let output = AVCaptureMetadataOutput() if session.canAddOutput(output) { session.addOutput(output) output.setMetadataObjectsDelegate(context.coordinator, queue: .main) output.metadataObjectTypes = [.qr] } let preview = AVCaptureVideoPreviewLayer(session: session) preview.videoGravity = .resizeAspectFill preview.frame = view.bounds view.layer.addSublayer(preview) context.coordinator.previewLayer = preview DispatchQueue.global(qos: .userInitiated).async { session.startRunning() } context.coordinator.session = session return view } func updateUIView(_ uiView: UIView, context: Context) { context.coordinator.previewLayer?.frame = uiView.bounds } func makeCoordinator() -> Coordinator { Coordinator(onResult: onResult) } final class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate { var onResult: (String) -> Void var session: AVCaptureSession? var previewLayer: AVCaptureVideoPreviewLayer? private var didCapture = false init(onResult: @escaping (String) -> Void) { self.onResult = onResult } func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput objects: [AVMetadataObject], from connection: AVCaptureConnection) { guard !didCapture, let obj = objects.first as? AVMetadataMachineReadableCodeObject, let str = obj.stringValue else { return } didCapture = true session?.stopRunning() DispatchQueue.main.async { self.onResult(str) } } } }