SimpleQRCodeScannerView.swift 4.4 KB

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