87 lines
2.9 KiB
Swift
87 lines
2.9 KiB
Swift
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) }
|
|
}
|
|
}
|
|
}
|