@cleanuidev/react-native-scanner 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +609 -0
- package/Scanner.podspec +20 -0
- package/android/build.gradle +90 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/com/scanner/CameraInfoModule.kt +253 -0
- package/android/src/main/java/com/scanner/ScannerPackage.kt +21 -0
- package/android/src/main/java/com/scanner/ScannerView.kt +783 -0
- package/android/src/main/java/com/scanner/ScannerViewManager.kt +181 -0
- package/android/src/main/java/com/scanner/utils/BarcodeFrameManager.kt +170 -0
- package/android/src/main/java/com/scanner/views/BarcodeFrameOverlayView.kt +43 -0
- package/android/src/main/java/com/scanner/views/FocusAreaView.kt +124 -0
- package/ios/BarcodeDetectionManager.swift +229 -0
- package/ios/BarcodeFrameManager.swift +175 -0
- package/ios/BarcodeFrameOverlayView.swift +102 -0
- package/ios/CameraManager.swift +396 -0
- package/ios/CoordinateTransformer.swift +140 -0
- package/ios/FocusAreaOverlayView.swift +161 -0
- package/ios/Models.swift +341 -0
- package/ios/Protocols.swift +194 -0
- package/ios/ScannerView.h +14 -0
- package/ios/ScannerView.mm +358 -0
- package/ios/ScannerViewImpl.swift +580 -0
- package/ios/react-native-scanner-Bridging-Header.h +26 -0
- package/lib/module/CameraInfoModule.js +8 -0
- package/lib/module/CameraInfoModule.js.map +1 -0
- package/lib/module/ScannerViewNativeComponent.ts +121 -0
- package/lib/module/hooks/useCameraInfo.js +106 -0
- package/lib/module/hooks/useCameraInfo.js.map +1 -0
- package/lib/module/index.js +13 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +47 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/CameraInfoModule.d.ts +8 -0
- package/lib/typescript/src/CameraInfoModule.d.ts.map +1 -0
- package/lib/typescript/src/ScannerViewNativeComponent.d.ts +91 -0
- package/lib/typescript/src/ScannerViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useCameraInfo.d.ts +25 -0
- package/lib/typescript/src/hooks/useCameraInfo.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +8 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +145 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +178 -0
- package/src/CameraInfoModule.ts +11 -0
- package/src/ScannerViewNativeComponent.ts +121 -0
- package/src/hooks/useCameraInfo.ts +190 -0
- package/src/index.tsx +30 -0
- package/src/types.ts +177 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BarcodeDetectionManager.swift
|
|
3
|
+
// react-native-scanner
|
|
4
|
+
//
|
|
5
|
+
// Manages barcode detection using Vision framework
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import Vision
|
|
10
|
+
import AVFoundation
|
|
11
|
+
|
|
12
|
+
/// Manages barcode detection using Vision framework
|
|
13
|
+
class BarcodeDetectionManager: NSObject, BarcodeScannerProtocol {
|
|
14
|
+
|
|
15
|
+
// MARK: - Public Properties
|
|
16
|
+
|
|
17
|
+
/// Delegate for detection events
|
|
18
|
+
weak var delegate: BarcodeDetectionDelegate?
|
|
19
|
+
|
|
20
|
+
// MARK: - Private Properties
|
|
21
|
+
|
|
22
|
+
/// Supported barcode symbologies
|
|
23
|
+
private var supportedSymbologies: [VNBarcodeSymbology]
|
|
24
|
+
|
|
25
|
+
/// Scan strategy
|
|
26
|
+
private var scanStrategy: BarcodeScanStrategy
|
|
27
|
+
|
|
28
|
+
/// Whether scanning is paused
|
|
29
|
+
private var scanningPaused: Bool
|
|
30
|
+
|
|
31
|
+
/// Background queue for detection
|
|
32
|
+
private let detectionQueue: DispatchQueue
|
|
33
|
+
|
|
34
|
+
// MARK: - Initialization
|
|
35
|
+
|
|
36
|
+
override init() {
|
|
37
|
+
// Default: all supported formats
|
|
38
|
+
self.supportedSymbologies = [
|
|
39
|
+
.qr, .code128, .code39, .ean13, .ean8,
|
|
40
|
+
.upce, .pdf417, .aztec, .dataMatrix, .itf14
|
|
41
|
+
]
|
|
42
|
+
self.scanStrategy = .defaultStrategy
|
|
43
|
+
self.scanningPaused = false
|
|
44
|
+
self.detectionQueue = DispatchQueue(label: "com.scanner.detection")
|
|
45
|
+
|
|
46
|
+
super.init()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// MARK: - Public Methods
|
|
50
|
+
|
|
51
|
+
/// Detect barcodes in a sample buffer
|
|
52
|
+
/// - Parameter sampleBuffer: The sample buffer to analyze
|
|
53
|
+
func detectBarcodes(in sampleBuffer: CMSampleBuffer) {
|
|
54
|
+
guard !scanningPaused else { return }
|
|
55
|
+
|
|
56
|
+
guard let pixelBuffer = getPixelBuffer(from: sampleBuffer) else {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let request = createDetectionRequest()
|
|
61
|
+
let orientation = getImageOrientation(from: sampleBuffer)
|
|
62
|
+
|
|
63
|
+
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer,
|
|
64
|
+
orientation: orientation,
|
|
65
|
+
options: [:])
|
|
66
|
+
|
|
67
|
+
detectionQueue.async { [weak self] in
|
|
68
|
+
do {
|
|
69
|
+
try handler.perform([request])
|
|
70
|
+
} catch {
|
|
71
|
+
print("[BarcodeDetectionManager] Detection failed: \(error)")
|
|
72
|
+
DispatchQueue.main.async {
|
|
73
|
+
self?.delegate?.barcodeDetectionManager(self!, didFailWith: error)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Detect barcodes in a CMSampleBuffer with completion
|
|
80
|
+
/// - Parameters:
|
|
81
|
+
/// - sampleBuffer: The sample buffer to analyze
|
|
82
|
+
/// - completion: Completion handler with observations
|
|
83
|
+
func detectBarcodes(in sampleBuffer: CMSampleBuffer,
|
|
84
|
+
completion: @escaping ([VNBarcodeObservation]) -> Void) {
|
|
85
|
+
guard !scanningPaused else {
|
|
86
|
+
completion([])
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
guard let pixelBuffer = getPixelBuffer(from: sampleBuffer) else {
|
|
91
|
+
completion([])
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let request = VNDetectBarcodesRequest { request, error in
|
|
96
|
+
if let error = error {
|
|
97
|
+
print("[BarcodeDetectionManager] Detection error: \(error)")
|
|
98
|
+
completion([])
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
guard let results = request.results as? [VNBarcodeObservation] else {
|
|
103
|
+
completion([])
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
completion(results)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
request.symbologies = supportedSymbologies
|
|
111
|
+
|
|
112
|
+
let orientation = getImageOrientation(from: sampleBuffer)
|
|
113
|
+
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer,
|
|
114
|
+
orientation: orientation,
|
|
115
|
+
options: [:])
|
|
116
|
+
|
|
117
|
+
detectionQueue.async {
|
|
118
|
+
do {
|
|
119
|
+
try handler.perform([request])
|
|
120
|
+
} catch {
|
|
121
|
+
print("[BarcodeDetectionManager] Detection failed: \(error)")
|
|
122
|
+
completion([])
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// MARK: - BarcodeScannerProtocol Methods
|
|
128
|
+
|
|
129
|
+
/// Pause barcode scanning
|
|
130
|
+
func pauseScanning() {
|
|
131
|
+
scanningPaused = true
|
|
132
|
+
print("[BarcodeDetectionManager] Scanning paused")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Resume barcode scanning
|
|
136
|
+
func resumeScanning() {
|
|
137
|
+
scanningPaused = false
|
|
138
|
+
print("[BarcodeDetectionManager] Scanning resumed")
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Check if scanning is paused
|
|
142
|
+
/// - Returns: True if scanning is paused
|
|
143
|
+
func isScanningPaused() -> Bool {
|
|
144
|
+
// Implementation: Return paused state
|
|
145
|
+
return scanningPaused
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Set the barcode formats to detect
|
|
149
|
+
/// - Parameter formats: Array of barcode formats
|
|
150
|
+
func setBarcodeFormats(_ formats: [BarcodeFormat]) {
|
|
151
|
+
if formats.isEmpty {
|
|
152
|
+
// If empty, use all supported formats
|
|
153
|
+
supportedSymbologies = [
|
|
154
|
+
.qr, .code128, .code39, .ean13, .ean8,
|
|
155
|
+
.upce, .pdf417, .aztec, .dataMatrix, .itf14
|
|
156
|
+
]
|
|
157
|
+
} else {
|
|
158
|
+
supportedSymbologies = formats.map { $0.visionSymbology }
|
|
159
|
+
}
|
|
160
|
+
print("[BarcodeDetectionManager] Barcode formats updated: \(supportedSymbologies)")
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// Set the scan strategy
|
|
164
|
+
/// - Parameter strategy: The scan strategy to use
|
|
165
|
+
func setScanStrategy(_ strategy: BarcodeScanStrategy) {
|
|
166
|
+
scanStrategy = strategy
|
|
167
|
+
print("[BarcodeDetectionManager] Scan strategy set to: \(strategy.rawValue)")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// MARK: - Private Methods
|
|
171
|
+
|
|
172
|
+
/// Create a barcode detection request
|
|
173
|
+
/// - Returns: Configured VNDetectBarcodesRequest
|
|
174
|
+
private func createDetectionRequest() -> VNDetectBarcodesRequest {
|
|
175
|
+
let request = VNDetectBarcodesRequest { [weak self] request, error in
|
|
176
|
+
self?.handleDetectionResults(request: request, error: error)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
request.symbologies = supportedSymbologies
|
|
180
|
+
|
|
181
|
+
return request
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// Process detection results
|
|
185
|
+
/// - Parameters:
|
|
186
|
+
/// - request: The completed request
|
|
187
|
+
/// - error: Any error that occurred
|
|
188
|
+
private func handleDetectionResults(request: VNRequest, error: Error?) {
|
|
189
|
+
if let error = error {
|
|
190
|
+
print("[BarcodeDetectionManager] Detection error: \(error)")
|
|
191
|
+
DispatchQueue.main.async { [weak self] in
|
|
192
|
+
guard let self = self else { return }
|
|
193
|
+
self.delegate?.barcodeDetectionManager(self, didFailWith: error)
|
|
194
|
+
}
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
guard let results = request.results as? [VNBarcodeObservation] else {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Filter out barcodes without valid payload
|
|
203
|
+
let validBarcodes = results.filter { $0.payloadStringValue != nil }
|
|
204
|
+
|
|
205
|
+
if !validBarcodes.isEmpty {
|
|
206
|
+
DispatchQueue.main.async { [weak self] in
|
|
207
|
+
guard let self = self else { return }
|
|
208
|
+
self.delegate?.barcodeDetectionManager(self, didDetect: validBarcodes)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// Get pixel buffer from sample buffer
|
|
214
|
+
/// - Parameter sampleBuffer: The sample buffer
|
|
215
|
+
/// - Returns: Pixel buffer or nil
|
|
216
|
+
private func getPixelBuffer(from sampleBuffer: CMSampleBuffer) -> CVPixelBuffer? {
|
|
217
|
+
return CMSampleBufferGetImageBuffer(sampleBuffer)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Get image orientation from sample buffer
|
|
221
|
+
/// - Parameter sampleBuffer: The sample buffer
|
|
222
|
+
/// - Returns: CGImagePropertyOrientation
|
|
223
|
+
private func getImageOrientation(from sampleBuffer: CMSampleBuffer) -> CGImagePropertyOrientation {
|
|
224
|
+
// For most cases, .up works well
|
|
225
|
+
// In production, you might want to account for device orientation
|
|
226
|
+
return .up
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BarcodeFrameManager.swift
|
|
3
|
+
// react-native-scanner
|
|
4
|
+
//
|
|
5
|
+
// Manages barcode frame lifecycle with timeout
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import CoreGraphics
|
|
10
|
+
|
|
11
|
+
/// Manages barcode frames with automatic timeout and cleanup
|
|
12
|
+
class BarcodeFrameManager {
|
|
13
|
+
|
|
14
|
+
// MARK: - Public Properties
|
|
15
|
+
|
|
16
|
+
/// Delegate for frame change notifications
|
|
17
|
+
weak var delegate: BarcodeFrameManagerDelegate?
|
|
18
|
+
|
|
19
|
+
/// Callback for frame changes
|
|
20
|
+
var onFramesChanged: (([CGRect]) -> Void)?
|
|
21
|
+
|
|
22
|
+
// MARK: - Private Properties
|
|
23
|
+
|
|
24
|
+
/// Active barcode frames mapped by barcode data
|
|
25
|
+
private var activeFrames: [String: BarcodeFrame] = [:]
|
|
26
|
+
|
|
27
|
+
/// Timeout duration for frames (seconds)
|
|
28
|
+
private let frameTimeout: TimeInterval
|
|
29
|
+
|
|
30
|
+
/// Timer for cleanup
|
|
31
|
+
private var cleanupTimer: Timer?
|
|
32
|
+
|
|
33
|
+
/// Queue for thread-safe access
|
|
34
|
+
private let queue: DispatchQueue
|
|
35
|
+
|
|
36
|
+
// MARK: - Initialization
|
|
37
|
+
|
|
38
|
+
init(frameTimeout: TimeInterval = 1.0) {
|
|
39
|
+
self.frameTimeout = frameTimeout
|
|
40
|
+
self.queue = DispatchQueue(label: "com.scanner.framemanager", attributes: .concurrent)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// MARK: - Public Methods
|
|
44
|
+
|
|
45
|
+
/// Update barcode frames with new detections
|
|
46
|
+
/// - Parameter frames: Dictionary mapping barcode data to rectangles
|
|
47
|
+
func updateBarcodeFrames(_ frames: [String: CGRect]) {
|
|
48
|
+
queue.async(flags: .barrier) { [weak self] in
|
|
49
|
+
guard let self = self else { return }
|
|
50
|
+
|
|
51
|
+
let now = Date()
|
|
52
|
+
let currentKeys = Set(frames.keys)
|
|
53
|
+
|
|
54
|
+
// Update or add frames with current timestamp
|
|
55
|
+
for (key, rect) in frames {
|
|
56
|
+
self.activeFrames[key] = BarcodeFrame(rect: rect, lastSeenTime: now)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Remove frames not in current detection
|
|
60
|
+
self.activeFrames = self.activeFrames.filter { currentKeys.contains($0.key) }
|
|
61
|
+
|
|
62
|
+
// Schedule cleanup if we have frames
|
|
63
|
+
if !self.activeFrames.isEmpty {
|
|
64
|
+
DispatchQueue.main.async {
|
|
65
|
+
self.scheduleCleanup()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Notify observers
|
|
70
|
+
self.notifyFramesChanged()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Get current active frames for display
|
|
75
|
+
/// - Returns: Array of active frame rectangles
|
|
76
|
+
func getActiveFrames() -> [CGRect] {
|
|
77
|
+
return queue.sync {
|
|
78
|
+
return activeFrames.values.map { $0.rect }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Clear all barcode frames immediately
|
|
83
|
+
func clearAllFrames() {
|
|
84
|
+
queue.async(flags: .barrier) { [weak self] in
|
|
85
|
+
guard let self = self else { return }
|
|
86
|
+
|
|
87
|
+
let hadFrames = !self.activeFrames.isEmpty
|
|
88
|
+
self.activeFrames.removeAll()
|
|
89
|
+
|
|
90
|
+
DispatchQueue.main.async {
|
|
91
|
+
self.cleanupTimer?.invalidate()
|
|
92
|
+
self.cleanupTimer = nil
|
|
93
|
+
|
|
94
|
+
if hadFrames {
|
|
95
|
+
self.notifyFramesChanged()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Shutdown the manager and cleanup resources
|
|
102
|
+
func shutdown() {
|
|
103
|
+
clearAllFrames()
|
|
104
|
+
print("[BarcodeFrameManager] Shutdown")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// MARK: - Private Methods
|
|
108
|
+
|
|
109
|
+
/// Schedule cleanup of stale frames
|
|
110
|
+
private func scheduleCleanup() {
|
|
111
|
+
// Cancel existing timer
|
|
112
|
+
cleanupTimer?.invalidate()
|
|
113
|
+
|
|
114
|
+
// Schedule new timer
|
|
115
|
+
cleanupTimer = Timer.scheduledTimer(
|
|
116
|
+
timeInterval: frameTimeout,
|
|
117
|
+
target: self,
|
|
118
|
+
selector: #selector(cleanupStaleFrames),
|
|
119
|
+
userInfo: nil,
|
|
120
|
+
repeats: false
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Clean up stale frames
|
|
125
|
+
@objc private func cleanupStaleFrames() {
|
|
126
|
+
queue.async(flags: .barrier) { [weak self] in
|
|
127
|
+
guard let self = self else { return }
|
|
128
|
+
|
|
129
|
+
let now = Date()
|
|
130
|
+
let countBefore = self.activeFrames.count
|
|
131
|
+
|
|
132
|
+
// Remove frames older than timeout
|
|
133
|
+
self.activeFrames = self.activeFrames.filter { _, frame in
|
|
134
|
+
now.timeIntervalSince(frame.lastSeenTime) < self.frameTimeout
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let framesRemoved = countBefore - self.activeFrames.count
|
|
138
|
+
|
|
139
|
+
if framesRemoved > 0 {
|
|
140
|
+
print("[BarcodeFrameManager] Removed \(framesRemoved) stale frame(s)")
|
|
141
|
+
self.notifyFramesChanged()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Reschedule if frames remain
|
|
145
|
+
if !self.activeFrames.isEmpty {
|
|
146
|
+
DispatchQueue.main.async {
|
|
147
|
+
self.scheduleCleanup()
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
print("[BarcodeFrameManager] No more frames to monitor")
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// Notify observers of frame changes
|
|
156
|
+
private func notifyFramesChanged() {
|
|
157
|
+
let currentFrames = activeFrames.values.map { $0.rect }
|
|
158
|
+
|
|
159
|
+
DispatchQueue.main.async { [weak self] in
|
|
160
|
+
guard let self = self else { return }
|
|
161
|
+
|
|
162
|
+
// Call delegate
|
|
163
|
+
self.delegate?.barcodeFrameManager(self, didUpdateFrames: currentFrames)
|
|
164
|
+
|
|
165
|
+
// Call callback
|
|
166
|
+
self.onFramesChanged?(currentFrames)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
deinit {
|
|
171
|
+
// Implementation: Cleanup timer
|
|
172
|
+
cleanupTimer?.invalidate()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BarcodeFrameOverlayView.swift
|
|
3
|
+
// react-native-scanner
|
|
4
|
+
//
|
|
5
|
+
// Overlay view for barcode frame visualization
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import UIKit
|
|
9
|
+
|
|
10
|
+
/// View that draws rectangles around detected barcodes
|
|
11
|
+
class BarcodeFrameOverlayView: UIView, BarcodeFrameDisplayProtocol {
|
|
12
|
+
|
|
13
|
+
// MARK: - Public Properties
|
|
14
|
+
|
|
15
|
+
/// Color for barcode frame borders
|
|
16
|
+
var frameColor: UIColor = .yellow {
|
|
17
|
+
didSet { setNeedsDisplay() }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Width of the frame stroke
|
|
21
|
+
var frameStrokeWidth: CGFloat = 3.0 {
|
|
22
|
+
didSet { setNeedsDisplay() }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// MARK: - Private Properties
|
|
26
|
+
|
|
27
|
+
/// Array of barcode rectangles to draw
|
|
28
|
+
private var barcodeBoxes: [CGRect] = [] {
|
|
29
|
+
didSet { setNeedsDisplay() }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// MARK: - Initialization
|
|
33
|
+
|
|
34
|
+
override init(frame: CGRect) {
|
|
35
|
+
super.init(frame: frame)
|
|
36
|
+
setupView()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
required init?(coder: NSCoder) {
|
|
40
|
+
super.init(coder: coder)
|
|
41
|
+
setupView()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private func setupView() {
|
|
45
|
+
// Implementation: Configure view properties
|
|
46
|
+
backgroundColor = .clear
|
|
47
|
+
isUserInteractionEnabled = false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// MARK: - Drawing
|
|
51
|
+
|
|
52
|
+
override func draw(_ rect: CGRect) {
|
|
53
|
+
super.draw(rect)
|
|
54
|
+
|
|
55
|
+
guard let context = UIGraphicsGetCurrentContext() else { return }
|
|
56
|
+
|
|
57
|
+
// Implementation: Draw rectangles for each barcode
|
|
58
|
+
drawBarcodeFrames(in: context)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Draw barcode frame rectangles
|
|
62
|
+
/// - Parameter context: Graphics context
|
|
63
|
+
private func drawBarcodeFrames(in context: CGContext) {
|
|
64
|
+
guard !barcodeBoxes.isEmpty else { return }
|
|
65
|
+
|
|
66
|
+
// Set stroke color and width
|
|
67
|
+
context.setStrokeColor(frameColor.cgColor)
|
|
68
|
+
context.setLineWidth(frameStrokeWidth)
|
|
69
|
+
context.setLineCap(.round)
|
|
70
|
+
context.setLineJoin(.round)
|
|
71
|
+
|
|
72
|
+
// Draw rectangle for each barcode box
|
|
73
|
+
for box in barcodeBoxes {
|
|
74
|
+
context.stroke(box)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// MARK: - BarcodeFrameDisplayProtocol Methods
|
|
79
|
+
|
|
80
|
+
/// Update the barcode frames configuration
|
|
81
|
+
/// - Parameter config: The new configuration
|
|
82
|
+
func updateBarcodeFrames(config: BarcodeFramesConfig) {
|
|
83
|
+
frameColor = config.color
|
|
84
|
+
setNeedsDisplay()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Set the barcode boxes to display
|
|
88
|
+
/// - Parameter boxes: Array of rectangles to display
|
|
89
|
+
func setBarcodeBoxes(_ boxes: [CGRect]) {
|
|
90
|
+
// Implementation: Update boxes on main thread
|
|
91
|
+
DispatchQueue.main.async { [weak self] in
|
|
92
|
+
self?.barcodeBoxes = boxes
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Clear all barcode boxes
|
|
97
|
+
func clearBarcodeBoxes() {
|
|
98
|
+
// Implementation: Clear boxes array
|
|
99
|
+
setBarcodeBoxes([])
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|