SimpleQRCodeScannerView.swift 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // LoopFollow
  2. // SimpleQRCodeScannerView.swift
  3. import AVFoundation
  4. import SwiftUI
  5. import UIKit
  6. struct SimpleQRCodeScannerView: UIViewControllerRepresentable {
  7. @Environment(\.presentationMode) var presentationMode
  8. var completion: (Result<String, Error>) -> Void
  9. func makeUIViewController(context _: Context) -> UINavigationController {
  10. let scannerVC = SimpleQRCodeScannerViewController { result in
  11. completion(result)
  12. }
  13. let navController = UINavigationController(rootViewController: scannerVC)
  14. // Apply dark mode if needed
  15. if Storage.shared.forceDarkMode.value {
  16. scannerVC.overrideUserInterfaceStyle = .dark
  17. navController.overrideUserInterfaceStyle = .dark
  18. }
  19. return navController
  20. }
  21. func updateUIViewController(_: UINavigationController, context _: Context) {}
  22. }
  23. class SimpleQRCodeScannerViewController: UIViewController {
  24. private var session: AVCaptureSession?
  25. private var completion: (Result<String, Error>) -> Void
  26. init(completion: @escaping (Result<String, Error>) -> Void) {
  27. self.completion = completion
  28. super.init(nibName: nil, bundle: nil)
  29. }
  30. @available(*, unavailable)
  31. required init?(coder _: NSCoder) {
  32. fatalError("init(coder:) has not been implemented")
  33. }
  34. override func viewDidLoad() {
  35. super.viewDidLoad()
  36. // Add cancel button
  37. navigationItem.rightBarButtonItem = UIBarButtonItem(
  38. barButtonSystemItem: .cancel,
  39. target: self,
  40. action: #selector(cancelTapped)
  41. )
  42. navigationItem.title = "Scan QR Code"
  43. setupCamera()
  44. }
  45. private func setupCamera() {
  46. let session = AVCaptureSession()
  47. self.session = session
  48. guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
  49. let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
  50. session.canAddInput(videoInput)
  51. else {
  52. let error = NSError(domain: "QRCodeScannerError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to set up camera input."])
  53. completion(.failure(error))
  54. return
  55. }
  56. session.addInput(videoInput)
  57. let metadataOutput = AVCaptureMetadataOutput()
  58. if session.canAddOutput(metadataOutput) {
  59. session.addOutput(metadataOutput)
  60. metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
  61. metadataOutput.metadataObjectTypes = [.qr]
  62. } else {
  63. let error = NSError(domain: "QRCodeScannerError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to set up metadata output."])
  64. completion(.failure(error))
  65. return
  66. }
  67. let previewLayer = AVCaptureVideoPreviewLayer(session: session)
  68. previewLayer.frame = view.layer.bounds
  69. previewLayer.videoGravity = .resizeAspectFill
  70. view.layer.addSublayer(previewLayer)
  71. DispatchQueue.global(qos: .userInitiated).async {
  72. session.startRunning()
  73. }
  74. }
  75. @objc private func cancelTapped() {
  76. DispatchQueue.global(qos: .userInitiated).async {
  77. if let session = self.session, session.isRunning {
  78. session.stopRunning()
  79. }
  80. }
  81. let error = NSError(domain: "QRCodeScannerError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Scanning cancelled by user."])
  82. completion(.failure(error))
  83. }
  84. override func viewDidLayoutSubviews() {
  85. super.viewDidLayoutSubviews()
  86. if let previewLayer = view.layer.sublayers?.first as? AVCaptureVideoPreviewLayer {
  87. previewLayer.frame = view.layer.bounds
  88. }
  89. }
  90. }
  91. extension SimpleQRCodeScannerViewController: AVCaptureMetadataOutputObjectsDelegate {
  92. func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
  93. if let session, session.isRunning {
  94. if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
  95. metadataObject.type == .qr,
  96. let stringValue = metadataObject.stringValue
  97. {
  98. DispatchQueue.global(qos: .userInitiated).async {
  99. session.stopRunning()
  100. }
  101. completion(.success(stringValue))
  102. }
  103. }
  104. }
  105. }