diff --git a/Sources/OSBarcodeLib/Models/OSBARCScanParameters.swift b/Sources/OSBarcodeLib/Models/OSBARCScanParameters.swift index 80ae389..9d6b1c8 100644 --- a/Sources/OSBarcodeLib/Models/OSBARCScanParameters.swift +++ b/Sources/OSBarcodeLib/Models/OSBARCScanParameters.swift @@ -14,15 +14,20 @@ public struct OSBARCScanParameters { // The optional hint, to scan a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. public let hint: OSBARCScannerHint? + // The optional list of hints, to restrict scanning to a specific set of formats (e.g. only qr code and ean-13). When non-empty, takes precedence over `hint`. `.unknown` anywhere in the list means it can scan all. + public let hints: [OSBARCScannerHint]? + public init(scanInstructions: String, scanButtonText: String?, cameraDirection: OSBARCCameraModel, scanOrientation: OSBARCOrientationModel, - hint: OSBARCScannerHint?) { + hint: OSBARCScannerHint?, + hints: [OSBARCScannerHint]? = nil) { self.scanInstructions = scanInstructions self.scanButtonText = scanButtonText self.cameraDirection = cameraDirection self.scanOrientation = scanOrientation self.hint = hint + self.hints = hints } } diff --git a/Sources/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift b/Sources/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift index 4cca08c..07ddec8 100644 --- a/Sources/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift +++ b/Sources/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift @@ -11,23 +11,27 @@ final class OSBARCCaptureOutputDecoder: NSObject, AVCaptureVideoDataOutputSample private let scanThroughButton: Bool /// Indicates if scanning is enabled (when there's a Scan Button). private var scanButtonEnabled: Bool - /// A hint, to scan a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. - private var hint: OSBARCScannerHint? + /// A list of hints, to restrict scanning to a specific set of formats (e.g. only qr code and ean-13). An empty list means it can scan all. + private var hints: [OSBARCScannerHint] /// The publisher's cancellable instance collector. private var cancellables: Set = [] + + convenience init(_ scanResult: Binding, _ scanThroughButton: Bool, _ scanButtonEnabled: Bool = false, andHint hint: OSBARCScannerHint? = nil) { + self.init(scanResult, scanThroughButton, scanButtonEnabled, andHints: hint.map { [$0] } ?? []) + } /// Constructor. /// - Parameters: /// - scanResult: Binding object with the value to return. /// - scanThroughButton: Boolean indicating if scanning should be performed automatically or after clicking the Scan Button. /// - scanButtonEnabled: Indicates if scanning has already been set on. - /// - hint: The optional hint, to scan a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. - init(_ scanResult: Binding, _ scanThroughButton: Bool, _ scanButtonEnabled: Bool = false, andHint hint: OSBARCScannerHint? = nil) { + /// - hints: The list of hints, to restrict scanning to a specific set of formats (e.g. only qr code and ean-13). An empty list means it can scan all. + init(_ scanResult: Binding, _ scanThroughButton: Bool, _ scanButtonEnabled: Bool = false, andHints hints: [OSBARCScannerHint]) { self._scanResult = scanResult self.scanThroughButton = scanThroughButton self.scanButtonEnabled = scanButtonEnabled - self.hint = hint + self.hints = hints super.init() NotificationCenter.default @@ -74,7 +78,7 @@ final class OSBARCCaptureOutputDecoder: NSObject, AVCaptureVideoDataOutputSample guard error == nil else { return } self.processClassification(for: request) }) - barcodeRequest.symbologies = (self.hint ?? .unknown).toVNBarcodeSymbologies() + barcodeRequest.symbologies = OSBARCScannerHint.toVNBarcodeSymbologies(from: self.hints) return barcodeRequest }() @@ -88,7 +92,7 @@ private extension OSBARCCaptureOutputDecoder { DispatchQueue.main.async { if let bestResult = request.results?.first as? VNBarcodeObservation, bestResult.confidence > 0.9, let payload = bestResult.payloadStringValue { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) - let format = OSBARCScannerHint.fromVNBarcodeSymbology(bestResult.symbology, withHint: self.hint) + let format = OSBARCScannerHint.fromVNBarcodeSymbology(bestResult.symbology, withHints: self.hints) self.scanResult = OSBARCScanResult(text: payload, format: format) } } diff --git a/Sources/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift b/Sources/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift index 03a6fe3..0e7e979 100644 --- a/Sources/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift +++ b/Sources/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift @@ -10,10 +10,29 @@ extension OSBARCScannerHint { } } + static func toVNBarcodeSymbologies(from hints: [OSBARCScannerHint]) -> [VNBarcodeSymbology] { + guard !hints.isEmpty else { return OSBARCScannerHint.unknown.toVNBarcodeSymbologies() } + var seen = Set() + var result: [VNBarcodeSymbology] = [] + for hint in hints { + for symbology in hint.toVNBarcodeSymbologies() where !seen.contains(symbology) { + seen.insert(symbology) + result.append(symbology) + } + } + return result + } + static func fromVNBarcodeSymbology(_ symbology: VNBarcodeSymbology, withHint hint: OSBARCScannerHint? = nil) -> OSBARCScannerHint { + return fromVNBarcodeSymbology(symbology, withHints: hint.map { [$0] } ?? []) + } + + static func fromVNBarcodeSymbology(_ symbology: VNBarcodeSymbology, withHints hints: [OSBARCScannerHint]) -> OSBARCScannerHint { if (symbology == .ean13) { // UPC-A and EAN-13 have similar format, and Apple Vision does not distinguish between the two // if a specific hint was provided, return that as the format + // when both are allowed by `hints` the returned format is ambiguous; we default to .ean13 to match legacy single-hint behavior + let hint: OSBARCScannerHint? = hints.contains(.ean13) ? .ean13 : (hints.contains(.upcA) ? .upcA : nil) switch hint { case .upcA: return .upcA case .ean13: return .ean13 diff --git a/Sources/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift b/Sources/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift index 8749efa..d04386a 100644 --- a/Sources/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift +++ b/Sources/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift @@ -32,10 +32,14 @@ final class OSBARCScannerBehaviour: OSBARCCoordinatable, OSBARCScannerProtocol { let buttonText = parameters.scanButtonText ?? "" // not having the button enabled is translated into having an empty text. let shouldShowButton = !buttonText.isEmpty // if empty text is passed, the button is not enabled on the scanner view + let rawHints = parameters.hints?.isEmpty == false ? parameters.hints! : [parameters.hint].compactMap { $0 } + // `.unknown` is the JS-side ALL sentinel; its presence forces scan-all (empty list). + let hints = rawHints.contains(.unknown) ? [] : rawHints + let barcodeDecoder = OSBARCCaptureOutputDecoder( scanResultBinding, shouldShowButton, - andHint: parameters.hint + andHints: hints ) let captureSessionManager = OSBARCCaptureSessionManager( parameters.cameraDirection,