Skip to content

Commit 438a8cd

Browse files
committed
Refactor QR code scanner to UIKit
1 parent d2f8e50 commit 438a8cd

File tree

3 files changed

+77
-36
lines changed

3 files changed

+77
-36
lines changed

LoopFollow/Helpers/Views/SimpleQRCodeScannerView.swift

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,88 +3,125 @@
33

44
import AVFoundation
55
import SwiftUI
6+
import UIKit
67

78
struct SimpleQRCodeScannerView: UIViewControllerRepresentable {
89
@Environment(\.presentationMode) var presentationMode
910
var completion: (Result<String, Error>) -> Void
1011

11-
// MARK: - Coordinator
12+
func makeUIViewController(context _: Context) -> UINavigationController {
13+
let scannerVC = SimpleQRCodeScannerViewController { result in
14+
completion(result)
15+
}
1216

13-
class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
14-
var parent: SimpleQRCodeScannerView
15-
var session: AVCaptureSession?
17+
let navController = UINavigationController(rootViewController: scannerVC)
1618

17-
init(parent: SimpleQRCodeScannerView) {
18-
self.parent = parent
19+
// Apply dark mode if needed
20+
if Storage.shared.forceDarkMode.value {
21+
scannerVC.overrideUserInterfaceStyle = .dark
22+
navController.overrideUserInterfaceStyle = .dark
1923
}
2024

21-
func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
22-
if let session, session.isRunning {
23-
if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
24-
metadataObject.type == .qr,
25-
let stringValue = metadataObject.stringValue
26-
{
27-
DispatchQueue.global(qos: .userInitiated).async {
28-
session.stopRunning()
29-
}
30-
parent.completion(.success(stringValue))
31-
}
32-
}
33-
}
25+
return navController
26+
}
27+
28+
func updateUIViewController(_: UINavigationController, context _: Context) {}
29+
}
30+
31+
class SimpleQRCodeScannerViewController: UIViewController {
32+
private var session: AVCaptureSession?
33+
private var completion: (Result<String, Error>) -> Void
34+
35+
init(completion: @escaping (Result<String, Error>) -> Void) {
36+
self.completion = completion
37+
super.init(nibName: nil, bundle: nil)
3438
}
3539

36-
// MARK: - UIViewControllerRepresentable Methods
40+
@available(*, unavailable)
41+
required init?(coder _: NSCoder) {
42+
fatalError("init(coder:) has not been implemented")
43+
}
44+
45+
override func viewDidLoad() {
46+
super.viewDidLoad()
47+
48+
// Add cancel button
49+
navigationItem.rightBarButtonItem = UIBarButtonItem(
50+
barButtonSystemItem: .cancel,
51+
target: self,
52+
action: #selector(cancelTapped)
53+
)
54+
navigationItem.title = "Scan QR Code"
3755

38-
func makeCoordinator() -> Coordinator {
39-
Coordinator(parent: self)
56+
setupCamera()
4057
}
4158

42-
func makeUIViewController(context: Context) -> UIViewController {
43-
let controller = UIViewController()
59+
private func setupCamera() {
4460
let session = AVCaptureSession()
45-
context.coordinator.session = session // Assign session to coordinator
61+
self.session = session
4662

4763
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
4864
let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
4965
session.canAddInput(videoInput)
5066
else {
5167
let error = NSError(domain: "QRCodeScannerError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to set up camera input."])
5268
completion(.failure(error))
53-
return controller
69+
return
5470
}
5571

5672
session.addInput(videoInput)
5773

5874
let metadataOutput = AVCaptureMetadataOutput()
5975
if session.canAddOutput(metadataOutput) {
6076
session.addOutput(metadataOutput)
61-
metadataOutput.setMetadataObjectsDelegate(context.coordinator, queue: DispatchQueue.main)
77+
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
6278
metadataOutput.metadataObjectTypes = [.qr]
6379
} else {
6480
let error = NSError(domain: "QRCodeScannerError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to set up metadata output."])
6581
completion(.failure(error))
66-
return controller
82+
return
6783
}
6884

6985
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
70-
previewLayer.frame = controller.view.layer.bounds
86+
previewLayer.frame = view.layer.bounds
7187
previewLayer.videoGravity = .resizeAspectFill
72-
controller.view.layer.addSublayer(previewLayer)
88+
view.layer.addSublayer(previewLayer)
7389

7490
DispatchQueue.global(qos: .userInitiated).async {
7591
session.startRunning()
7692
}
77-
78-
return controller
7993
}
8094

81-
func updateUIViewController(_: UIViewController, context _: Context) {}
82-
83-
func dismantleUIViewController(_: UIViewController, coordinator: Coordinator) {
95+
@objc private func cancelTapped() {
8496
DispatchQueue.global(qos: .userInitiated).async {
85-
if let session = coordinator.session, session.isRunning {
97+
if let session = self.session, session.isRunning {
8698
session.stopRunning()
8799
}
88100
}
101+
let error = NSError(domain: "QRCodeScannerError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Scanning cancelled by user."])
102+
completion(.failure(error))
103+
}
104+
105+
override func viewDidLayoutSubviews() {
106+
super.viewDidLayoutSubviews()
107+
if let previewLayer = view.layer.sublayers?.first as? AVCaptureVideoPreviewLayer {
108+
previewLayer.frame = view.layer.bounds
109+
}
110+
}
111+
}
112+
113+
extension SimpleQRCodeScannerViewController: AVCaptureMetadataOutputObjectsDelegate {
114+
func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
115+
if let session, session.isRunning {
116+
if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
117+
metadataObject.type == .qr,
118+
let stringValue = metadataObject.stringValue
119+
{
120+
DispatchQueue.global(qos: .userInitiated).async {
121+
session.stopRunning()
122+
}
123+
completion(.success(stringValue))
124+
}
125+
}
89126
}
90127
}

LoopFollow/Remote/Settings/RemoteSettingsView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// LoopFollow
22
// RemoteSettingsView.swift
33

4+
import AVFoundation
45
import HealthKit
56
import SwiftUI
7+
import UIKit
68

79
struct RemoteSettingsView: View {
810
@ObservedObject var viewModel: RemoteSettingsViewModel

LoopFollow/Settings/ImportExport/ImportExportSettingsView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// LoopFollow
22
// ImportExportSettingsView.swift
33

4+
import AVFoundation
45
import SwiftUI
6+
import UIKit
57

68
struct ImportExportSettingsView: View {
79
@StateObject private var viewModel = ImportExportSettingsViewModel()

0 commit comments

Comments
 (0)