From 5b96e23b327afd42a27692d3d86700cab4ff1f39 Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Fri, 22 Aug 2025 12:48:41 +0100 Subject: [PATCH 1/9] feat!: Support hint parameter in scanning References: https://outsystemsrd.atlassian.net/browse/RMET-2962 BREAKING CHANGE: This adds a new parameter to `scanBarcode` in `OSBARCManagerProtocol`, which is a breaking change --- .gitignore | 3 +- OSBarcodeLib.xcodeproj/project.pbxproj | 8 +++ OSBarcodeLib/Manager/OSBARCManager.swift | 14 ++++-- .../Manager/OSBARCManagerProtocol.swift | 9 +++- OSBarcodeLib/Models/OSBARCScannerHint.swift | 20 ++++++++ .../OSBARCCaptureOutputDecoder.swift | 17 +++---- ...OSBARCScannerHint+VNBarcodeSymbology.swift | 49 +++++++++++++++++++ .../Scanner/OSBARCScannerBehaviour.swift | 12 ++++- .../Scanner/OSBARCScannerProtocol.swift | 10 +++- .../Stubs/OSBARCScannerStub.swift | 1 + 10 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 OSBarcodeLib/Models/OSBARCScannerHint.swift create mode 100644 OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift diff --git a/.gitignore b/.gitignore index 5e19ded..6ae4980 100644 --- a/.gitignore +++ b/.gitignore @@ -91,4 +91,5 @@ iOSInjectionProject/ .swiftlint.yml scripts/build/ -.DS_Store \ No newline at end of file +build/ +.DS_Store diff --git a/OSBarcodeLib.xcodeproj/project.pbxproj b/OSBarcodeLib.xcodeproj/project.pbxproj index ee06ac1..b64325d 100644 --- a/OSBarcodeLib.xcodeproj/project.pbxproj +++ b/OSBarcodeLib.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 630812572E5866D900536FE7 /* OSBARCScannerHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 630812562E5866C700536FE7 /* OSBARCScannerHint.swift */; }; + 6308125D2E58902B00536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */; }; 7507FC1B27FC2AAE003809F6 /* OSBarcodeLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7507FC1227FC2AAE003809F6 /* OSBarcodeLib.framework */; }; 750B35872AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750B35862AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift */; }; 7513C4852B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7513C4802B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift */; }; @@ -70,6 +72,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 630812562E5866C700536FE7 /* OSBARCScannerHint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScannerHint.swift; sourceTree = ""; }; + 6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSBARCScannerHint+VNBarcodeSymbology.swift"; sourceTree = ""; }; 7507FC1227FC2AAE003809F6 /* OSBarcodeLib.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OSBarcodeLib.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7507FC1A27FC2AAE003809F6 /* OSBarcodeLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OSBarcodeLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 750B35862AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScannerViewConfigurationValues.swift; sourceTree = ""; }; @@ -212,6 +216,7 @@ 758E6C152B0238FF00FC16D9 /* OSBARCCameraModel.swift */, 7513C4802B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift */, 7513C4812B03E86B005E81C4 /* OSBARCOrientationModel.swift */, + 630812562E5866C700536FE7 /* OSBARCScannerHint.swift */, ); path = Models; sourceTree = ""; @@ -219,6 +224,7 @@ 758E6C172B0239C100FC16D9 /* Extensions */ = { isa = PBXGroup; children = ( + 6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */, 758E6C182B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift */, 7513C48F2B03E922005E81C4 /* AVCaptureVideoOrientation+CustomInit.swift */, 75183A152B7389EC00AFC687 /* Float+DecimalPlacesCleaner.swift */, @@ -436,6 +442,7 @@ buildActionMask = 2147483647; files = ( 75D20FEB2AF17C8E009AD84D /* OSBARCPermissionsBehaviour.swift in Sources */, + 630812572E5866D900536FE7 /* OSBARCScannerHint.swift in Sources */, 750B35872AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift in Sources */, 75183A162B7389EC00AFC687 /* Float+DecimalPlacesCleaner.swift in Sources */, 756E17452B754B6400D594DA /* OSBARCCameraManager.swift in Sources */, @@ -475,6 +482,7 @@ 7513C4872B03E86B005E81C4 /* OSBARCDeviceTypeModelMappable.swift in Sources */, 75EF59A02B0E44660084F144 /* OSBARCInstructionsText.swift in Sources */, 7513C4882B03E86B005E81C4 /* OSBARCModelMappable.swift in Sources */, + 6308125D2E58902B00536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift in Sources */, 7513C4862B03E86B005E81C4 /* OSBARCOrientationModel.swift in Sources */, 75EF599A2B0E2F220084F144 /* OSBARCBackgroundView.swift in Sources */, 7513C4852B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift in Sources */, diff --git a/OSBarcodeLib/Manager/OSBARCManager.swift b/OSBarcodeLib/Manager/OSBARCManager.swift index eee03cb..9511ad1 100644 --- a/OSBarcodeLib/Manager/OSBARCManager.swift +++ b/OSBarcodeLib/Manager/OSBARCManager.swift @@ -33,13 +33,19 @@ struct OSBARCManager { /// Implementation of the `OSBARCManagerProtocol` methods. extension OSBARCManager: OSBARCManagerProtocol { - func scanBarcode(with instructionsText: String, _ buttonText: String?, _ cameraModel: OSBARCCameraModel, and orientationModel: OSBARCOrientationModel) async throws -> String { + func scanBarcode( + with instructionsText: String, + _ buttonText: String?, + _ cameraModel: OSBARCCameraModel, + and orientationModel: OSBARCOrientationModel, + andHint hint: OSBARCScannerHint? + ) async throws -> String { // validates if the user has access to the device's camera. let hasCameraAccess = await self.permissionsBehaviour.hasCameraAccess() if !hasCameraAccess { throw OSBARCManagerError.cameraAccessDenied } // requests the scanner to start, treating its result value. return try await withCheckedThrowingContinuation { - self.startScanning(with: instructionsText, buttonText, cameraModel, and: orientationModel, continuation: $0) + self.startScanning(with: instructionsText, buttonText, cameraModel, and: orientationModel, andHint: hint, continuation: $0) } } @@ -49,16 +55,18 @@ extension OSBARCManager: OSBARCManagerProtocol { /// - buttonText: Text to be displayed for the scan button, if this is configured. `Nil` value means that the button will not be shown. /// - cameraModel: Camera to use for input gathering. /// - orientationModel: Scanner view's orientation. + /// - hint: The optional hint, to scan a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. /// - continuation: Object responsible for returning the method's result to its caller. private func startScanning( with instructionsText: String, _ buttonText: String?, _ cameraModel: OSBARCCameraModel, and orientationModel: OSBARCOrientationModel, + andHint hint: OSBARCScannerHint?, continuation: CheckedContinuation ) { DispatchQueue.main.async { - self.scannerBehaviour.startScanning(with: instructionsText, buttonText, cameraModel, and: orientationModel) { scannedCode in + self.scannerBehaviour.startScanning(with: instructionsText, buttonText, cameraModel, and: orientationModel, andHint: hint) { scannedCode in if !scannedCode.isEmpty { continuation.resume(returning: scannedCode) } else { diff --git a/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift b/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift index 53490f3..65dc7c6 100644 --- a/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift +++ b/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift @@ -9,6 +9,13 @@ public protocol OSBARCManagerProtocol { /// - buttonText: Text to be displayed for the scan button, if this is configured. `Nil` value means that the button will not be shown. /// - cameraModel: Camera to use for input gathering. /// - orientationModel: Scanner view's orientation. + /// - hint: The optional hint, to scan a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. /// - Returns: When successful, it returns the text associated with the scanned barcode. - func scanBarcode(with instructionsText: String, _ buttonText: String?, _ cameraModel: OSBARCCameraModel, and orientationModel: OSBARCOrientationModel) async throws -> String + func scanBarcode( + with instructionsText: String, + _ buttonText: String?, + _ cameraModel: OSBARCCameraModel, + and orientationModel: OSBARCOrientationModel, + andHint hint: OSBARCScannerHint? + ) async throws -> String } diff --git a/OSBarcodeLib/Models/OSBARCScannerHint.swift b/OSBarcodeLib/Models/OSBARCScannerHint.swift new file mode 100644 index 0000000..5fcb623 --- /dev/null +++ b/OSBarcodeLib/Models/OSBARCScannerHint.swift @@ -0,0 +1,20 @@ +public enum OSBARCScannerHint: Int { + case qrCode = 0 + case aztec + case codabar + case code39 + case code93 + case code128 + case dataMatrix + case maxicode + case itf + case ean13 + case ean8 + case pdf417 + case rss14 + case rssExpanded + case upcA + case upcE + case upcEanExtension + case unknown +} diff --git a/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift b/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift index c47f5c6..b2a9717 100644 --- a/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift +++ b/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift @@ -11,28 +11,23 @@ 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? /// The publisher's cancellable instance collector. private var cancellables: Set = [] - /// List of barcode types the scanner is looking for. - lazy private var barcodeTypes: [VNBarcodeSymbology] = { - var result: [VNBarcodeSymbology] = [.upce, .ean8, .ean13, .code39, .code93, .code128, .itf14, .qr, .dataMatrix, .pdf417, .aztec, .i2of5] - if #available(iOS 15.0, *) { // these types are only available from iOS 15 onwards. - result += [.codabar, .gs1DataBar, .gs1DataBarExpanded, .microPDF417, .microQR] - } - return result - }() - /// 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. - init(_ scanResult: Binding, _ scanThroughButton: Bool, _ scanButtonEnabled: Bool = false) { + /// - 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) { self._scanResult = scanResult self.scanThroughButton = scanThroughButton self.scanButtonEnabled = scanButtonEnabled + self.hint = hint super.init() NotificationCenter.default @@ -79,7 +74,7 @@ final class OSBARCCaptureOutputDecoder: NSObject, AVCaptureVideoDataOutputSample guard error == nil else { return } self.processClassification(for: request) }) - barcodeRequest.symbologies = self.barcodeTypes + barcodeRequest.symbologies = (self.hint ?? .unknown).toVNBarcodeSymbologies() return barcodeRequest }() diff --git a/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift b/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift new file mode 100644 index 0000000..2ca2058 --- /dev/null +++ b/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift @@ -0,0 +1,49 @@ +import Foundation +import Vision + +extension OSBARCScannerHint { + func toVNBarcodeSymbologies() -> [VNBarcodeSymbology] { + if let specificSymbiology = self.toVNBarcodeSymbology() { + return specificSymbiology + } else { + return self.allBarcodeTypes + } + } + + static let hintMappings: [OSBARCScannerHint: [VNBarcodeSymbology]] = { + var result: [OSBARCScannerHint: [VNBarcodeSymbology]] = [ + .qrCode: [.qr], + .aztec: [.aztec], + .code39: [.code39], + .code93: [.code93], + .code128: [.code128], + .dataMatrix: [.dataMatrix], + .itf: [.itf14, .i2of5], + .ean13: [.ean13], + .ean8: [.ean8], + .pdf417: [.pdf417], + .upcA: [.ean13], + .upcE: [.upce] + ] + + if #available(iOS 15.0, *) { + result[.codabar] = [.codabar] + result[.rss14] = [.gs1DataBar] + result[.rssExpanded] = [.gs1DataBarExpanded] + } + + return result + }() + + private func toVNBarcodeSymbology() -> [VNBarcodeSymbology]? { + return Self.hintMappings[self] + } + + private var allBarcodeTypes: [VNBarcodeSymbology] { + var result = Self.hintMappings.values.flatMap { $0 } + if #available(iOS 15.0, *) { + result += [.microPDF417, .microQR] + } + return result + } +} diff --git a/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift b/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift index 0db9fe2..d9cfb06 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift @@ -10,7 +10,14 @@ final class OSBARCScannerBehaviour: OSBARCCoordinatable, OSBARCScannerProtocol { /// The publisher's cancellable instance collector. private var cancellables: Set = [] - func startScanning(with instructionsText: String, _ buttonText: String?, _ cameraModel: OSBARCCameraModel, and orientationModel: OSBARCOrientationModel, _ completion: @escaping (String) -> Void) { + func startScanning( + with instructionsText: String, + _ buttonText: String?, + _ cameraModel: OSBARCCameraModel, + and orientationModel: OSBARCOrientationModel, + andHint hint: OSBARCScannerHint?, + _ completion: @escaping (String) -> Void + ) { $scanResult .dropFirst() // drops the first value - the empty string .first() // only publishes the first barcode value found @@ -34,7 +41,8 @@ final class OSBARCScannerBehaviour: OSBARCCoordinatable, OSBARCScannerProtocol { let barcodeDecoder = OSBARCCaptureOutputDecoder( scanResultBinding, - shouldShowButton + shouldShowButton, + andHint: hint ) let captureSessionManager = OSBARCCaptureSessionManager( cameraModel, diff --git a/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift b/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift index 53dc6c1..756e3cc 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift @@ -6,6 +6,14 @@ protocol OSBARCScannerProtocol { /// - buttonText: Text to be displayed for the scan button, if this is configured. `Nil` value means that the button will not be shown. /// - cameraModel: Camera to use for input gathering. /// - orientationModel: Scanner view's orientation. + /// - hint: The optional hint, to scan a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. /// - completion: The value returned or empty string in case the view is closed with no code scanned. - func startScanning(with instructionsText: String, _ buttonText: String?, _ cameraModel: OSBARCCameraModel, and orientationModel: OSBARCOrientationModel, _ completion: @escaping (String) -> Void) + func startScanning( + with instructionsText: String, + _ buttonText: String?, + _ cameraModel: OSBARCCameraModel, + and orientationModel: OSBARCOrientationModel, + andHint hint: OSBARCScannerHint?, + _ completion: @escaping (String) -> Void + ) } diff --git a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift index b6237b2..3a0ad13 100644 --- a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift +++ b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift @@ -8,6 +8,7 @@ final class OSBARCScannerStub: OSBARCScannerProtocol { _ buttonText: String?, _ cameraModel: OSBARCCameraModel, and orientationModel: OSBARCOrientationModel, + andHint hint: OSBARCScannerHint?, _ completion: @escaping (String) -> Void ) { completion(self.scanCancelled ? "" : OSBARCScannerStubValues.scannedCode) From 4898eac110129df85d4afaa64c0ac3005079e44d Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Fri, 22 Aug 2025 13:50:00 +0100 Subject: [PATCH 2/9] refactor!: Extract scan parameters to structure References: https://outsystemsrd.atlassian.net/browse/RMET-2962 BREAKING CHANGE: This modifies the signature of the public `scanBarcode`, but allows for easier addition of new parameters to avoid future breaking changes. --- OSBarcodeLib.xcodeproj/project.pbxproj | 4 +++ OSBarcodeLib/Manager/OSBARCManager.swift | 22 ++++----------- .../Manager/OSBARCManagerProtocol.swift | 12 ++------ .../Models/OSBARCScanParameters.swift | 28 +++++++++++++++++++ .../Scanner/OSBARCScannerBehaviour.swift | 18 +++++------- .../Scanner/OSBARCScannerProtocol.swift | 12 ++------ .../Stubs/OSBARCScannerStub.swift | 6 +--- 7 files changed, 49 insertions(+), 53 deletions(-) create mode 100644 OSBarcodeLib/Models/OSBARCScanParameters.swift diff --git a/OSBarcodeLib.xcodeproj/project.pbxproj b/OSBarcodeLib.xcodeproj/project.pbxproj index b64325d..f410536 100644 --- a/OSBarcodeLib.xcodeproj/project.pbxproj +++ b/OSBarcodeLib.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 630812572E5866D900536FE7 /* OSBARCScannerHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 630812562E5866C700536FE7 /* OSBARCScannerHint.swift */; }; 6308125D2E58902B00536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */; }; + 6308125F2E5891ED00536FE7 /* OSBARCScanParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6308125E2E5891E700536FE7 /* OSBARCScanParameters.swift */; }; 7507FC1B27FC2AAE003809F6 /* OSBarcodeLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7507FC1227FC2AAE003809F6 /* OSBarcodeLib.framework */; }; 750B35872AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750B35862AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift */; }; 7513C4852B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7513C4802B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift */; }; @@ -74,6 +75,7 @@ /* Begin PBXFileReference section */ 630812562E5866C700536FE7 /* OSBARCScannerHint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScannerHint.swift; sourceTree = ""; }; 6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSBARCScannerHint+VNBarcodeSymbology.swift"; sourceTree = ""; }; + 6308125E2E5891E700536FE7 /* OSBARCScanParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScanParameters.swift; sourceTree = ""; }; 7507FC1227FC2AAE003809F6 /* OSBarcodeLib.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OSBarcodeLib.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7507FC1A27FC2AAE003809F6 /* OSBarcodeLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OSBarcodeLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 750B35862AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScannerViewConfigurationValues.swift; sourceTree = ""; }; @@ -212,6 +214,7 @@ 758E6C142B0238F100FC16D9 /* Models */ = { isa = PBXGroup; children = ( + 6308125E2E5891E700536FE7 /* OSBARCScanParameters.swift */, 7513C4822B03E86B005E81C4 /* MappableProtocol */, 758E6C152B0238FF00FC16D9 /* OSBARCCameraModel.swift */, 7513C4802B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift */, @@ -481,6 +484,7 @@ 755AB5882B768425006B6507 /* OSBARCCaptureOutputDecoder.swift in Sources */, 7513C4872B03E86B005E81C4 /* OSBARCDeviceTypeModelMappable.swift in Sources */, 75EF59A02B0E44660084F144 /* OSBARCInstructionsText.swift in Sources */, + 6308125F2E5891ED00536FE7 /* OSBARCScanParameters.swift in Sources */, 7513C4882B03E86B005E81C4 /* OSBARCModelMappable.swift in Sources */, 6308125D2E58902B00536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift in Sources */, 7513C4862B03E86B005E81C4 /* OSBARCOrientationModel.swift in Sources */, diff --git a/OSBarcodeLib/Manager/OSBARCManager.swift b/OSBarcodeLib/Manager/OSBARCManager.swift index 9511ad1..deef76d 100644 --- a/OSBarcodeLib/Manager/OSBARCManager.swift +++ b/OSBarcodeLib/Manager/OSBARCManager.swift @@ -34,39 +34,27 @@ struct OSBARCManager { /// Implementation of the `OSBARCManagerProtocol` methods. extension OSBARCManager: OSBARCManagerProtocol { func scanBarcode( - with instructionsText: String, - _ buttonText: String?, - _ cameraModel: OSBARCCameraModel, - and orientationModel: OSBARCOrientationModel, - andHint hint: OSBARCScannerHint? + with parameters: OSBARCScanParameters ) async throws -> String { // validates if the user has access to the device's camera. let hasCameraAccess = await self.permissionsBehaviour.hasCameraAccess() if !hasCameraAccess { throw OSBARCManagerError.cameraAccessDenied } // requests the scanner to start, treating its result value. return try await withCheckedThrowingContinuation { - self.startScanning(with: instructionsText, buttonText, cameraModel, and: orientationModel, andHint: hint, continuation: $0) + self.startScanning(with: parameters, continuation: $0) } } /// Triggers the scanner view. /// - Parameters: - /// - instructionsText: Text to be displayed on the scanner view. - /// - buttonText: Text to be displayed for the scan button, if this is configured. `Nil` value means that the button will not be shown. - /// - cameraModel: Camera to use for input gathering. - /// - orientationModel: Scanner view's orientation. - /// - hint: The optional hint, to scan a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. + /// - parameters: The full parameter list to configure the scanner /// - continuation: Object responsible for returning the method's result to its caller. private func startScanning( - with instructionsText: String, - _ buttonText: String?, - _ cameraModel: OSBARCCameraModel, - and orientationModel: OSBARCOrientationModel, - andHint hint: OSBARCScannerHint?, + with parameters: OSBARCScanParameters, continuation: CheckedContinuation ) { DispatchQueue.main.async { - self.scannerBehaviour.startScanning(with: instructionsText, buttonText, cameraModel, and: orientationModel, andHint: hint) { scannedCode in + self.scannerBehaviour.startScanning(with: parameters) { scannedCode in if !scannedCode.isEmpty { continuation.resume(returning: scannedCode) } else { diff --git a/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift b/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift index 65dc7c6..e6dceb8 100644 --- a/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift +++ b/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift @@ -5,17 +5,9 @@ public protocol OSBARCManagerProtocol { /// `cameraAccessDenied`: If camera access has not been given. /// `scanningCancelled`: If scanning has been cancelled. /// - Parameters: - /// - instructionsText: Text to be displayed on the scanner view. - /// - buttonText: Text to be displayed for the scan button, if this is configured. `Nil` value means that the button will not be shown. - /// - cameraModel: Camera to use for input gathering. - /// - orientationModel: Scanner view's orientation. - /// - hint: The optional hint, to scan a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. + /// - parameters: The full parameter list to configure the scanner /// - Returns: When successful, it returns the text associated with the scanned barcode. func scanBarcode( - with instructionsText: String, - _ buttonText: String?, - _ cameraModel: OSBARCCameraModel, - and orientationModel: OSBARCOrientationModel, - andHint hint: OSBARCScannerHint? + with parameters: OSBARCScanParameters ) async throws -> String } diff --git a/OSBarcodeLib/Models/OSBARCScanParameters.swift b/OSBarcodeLib/Models/OSBARCScanParameters.swift new file mode 100644 index 0000000..80ae389 --- /dev/null +++ b/OSBarcodeLib/Models/OSBARCScanParameters.swift @@ -0,0 +1,28 @@ +public struct OSBARCScanParameters { + /// Text to be displayed on the scanner view. + public let scanInstructions: String + + /// Text to be displayed for the scan button, if this is configured. `Nil` value means that the button will not be shown. + public let scanButtonText: String? + + // Camera to use for input gathering. + public let cameraDirection: OSBARCCameraModel + + // Scanner view's orientation. + public let scanOrientation: OSBARCOrientationModel + + // 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? + + public init(scanInstructions: String, + scanButtonText: String?, + cameraDirection: OSBARCCameraModel, + scanOrientation: OSBARCOrientationModel, + hint: OSBARCScannerHint?) { + self.scanInstructions = scanInstructions + self.scanButtonText = scanButtonText + self.cameraDirection = cameraDirection + self.scanOrientation = scanOrientation + self.hint = hint + } +} diff --git a/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift b/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift index d9cfb06..45fcb5f 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift @@ -11,11 +11,7 @@ final class OSBARCScannerBehaviour: OSBARCCoordinatable, OSBARCScannerProtocol { private var cancellables: Set = [] func startScanning( - with instructionsText: String, - _ buttonText: String?, - _ cameraModel: OSBARCCameraModel, - and orientationModel: OSBARCOrientationModel, - andHint hint: OSBARCScannerHint?, + with parameters: OSBARCScanParameters, _ completion: @escaping (String) -> Void ) { $scanResult @@ -36,29 +32,29 @@ final class OSBARCScannerBehaviour: OSBARCCoordinatable, OSBARCScannerProtocol { } ) - let buttonText = buttonText ?? "" // not having the button enabled is translated into having an empty text. + 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 barcodeDecoder = OSBARCCaptureOutputDecoder( scanResultBinding, shouldShowButton, - andHint: hint + andHint: parameters.hint ) let captureSessionManager = OSBARCCaptureSessionManager( - cameraModel, - orientationModel, + parameters.cameraDirection, + parameters.scanOrientation, barcodeDecoder ) guard let viewModel: OSBARCScannerViewModel = try? .init(cameraManager: captureSessionManager) else { return completion("") } let scannerView = OSBARCScannerView( viewModel: viewModel, scanResult: scanResultBinding, - instructionsText: instructionsText, + instructionsText: parameters.scanInstructions, buttonText: buttonText, shouldShowButton: shouldShowButton, deviceType: UIDevice.current.userInterfaceIdiom.deviceTypeModel ) - let hostingController = OSBARCScannerViewHostingController(rootView: scannerView, orientationModel) + let hostingController = OSBARCScannerViewHostingController(rootView: scannerView, parameters.scanOrientation) hostingController.modalPresentationStyle = .fullScreen self.coordinator.present(hostingController) diff --git a/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift b/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift index 756e3cc..f5f7f1e 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift @@ -2,18 +2,10 @@ protocol OSBARCScannerProtocol { /// Triggers the scanner view that allows the barcode scan. /// - Parameters: - /// - instructionsText: Text to be displayed on the scanner view. - /// - buttonText: Text to be displayed for the scan button, if this is configured. `Nil` value means that the button will not be shown. - /// - cameraModel: Camera to use for input gathering. - /// - orientationModel: Scanner view's orientation. - /// - hint: The optional hint, to scan a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. + /// - parameters: The full parameter list to configure the scanner /// - completion: The value returned or empty string in case the view is closed with no code scanned. func startScanning( - with instructionsText: String, - _ buttonText: String?, - _ cameraModel: OSBARCCameraModel, - and orientationModel: OSBARCOrientationModel, - andHint hint: OSBARCScannerHint?, + with parameters: OSBARCScanParameters, _ completion: @escaping (String) -> Void ) } diff --git a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift index 3a0ad13..944678e 100644 --- a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift +++ b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift @@ -4,11 +4,7 @@ final class OSBARCScannerStub: OSBARCScannerProtocol { var scanCancelled: Bool = false func startScanning( - with instructionsText: String, - _ buttonText: String?, - _ cameraModel: OSBARCCameraModel, - and orientationModel: OSBARCOrientationModel, - andHint hint: OSBARCScannerHint?, + with parameters: OSBARCScanParameters, _ completion: @escaping (String) -> Void ) { completion(self.scanCancelled ? "" : OSBARCScannerStubValues.scannedCode) From 4b5901e17c9b9c15455b2ad278ed3ea91ed6a6ed Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Fri, 22 Aug 2025 18:26:40 +0100 Subject: [PATCH 3/9] feat!: return scanned text and format References: https://outsystemsrd.atlassian.net/browse/RMET-2962 BREAKING CHANGE: This modifies the output of the public `scanBarcode`, but allows for easier addition of new output to avoid future breaking changes. --- OSBarcodeLib.xcodeproj/project.pbxproj | 4 ++++ OSBarcodeLib/Manager/OSBARCManager.swift | 10 +++++----- OSBarcodeLib/Manager/OSBARCManagerProtocol.swift | 2 +- OSBarcodeLib/Models/OSBARCScanResult.swift | 13 +++++++++++++ .../CameraManager/OSBARCCaptureOutputDecoder.swift | 7 ++++--- .../OSBARCScannerHint+VNBarcodeSymbology.swift | 6 ++++++ OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift | 6 +++--- OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift | 2 +- OSBarcodeLib/Scanner/OSBARCScannerView.swift | 4 ++-- OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift | 4 ++-- 10 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 OSBarcodeLib/Models/OSBARCScanResult.swift diff --git a/OSBarcodeLib.xcodeproj/project.pbxproj b/OSBarcodeLib.xcodeproj/project.pbxproj index f410536..15f79e1 100644 --- a/OSBarcodeLib.xcodeproj/project.pbxproj +++ b/OSBarcodeLib.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 630812572E5866D900536FE7 /* OSBARCScannerHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 630812562E5866C700536FE7 /* OSBARCScannerHint.swift */; }; 6308125D2E58902B00536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */; }; 6308125F2E5891ED00536FE7 /* OSBARCScanParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6308125E2E5891E700536FE7 /* OSBARCScanParameters.swift */; }; + 630812612E589F5A00536FE7 /* OSBARCScanResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 630812602E589F5600536FE7 /* OSBARCScanResult.swift */; }; 7507FC1B27FC2AAE003809F6 /* OSBarcodeLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7507FC1227FC2AAE003809F6 /* OSBarcodeLib.framework */; }; 750B35872AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750B35862AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift */; }; 7513C4852B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7513C4802B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift */; }; @@ -76,6 +77,7 @@ 630812562E5866C700536FE7 /* OSBARCScannerHint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScannerHint.swift; sourceTree = ""; }; 6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSBARCScannerHint+VNBarcodeSymbology.swift"; sourceTree = ""; }; 6308125E2E5891E700536FE7 /* OSBARCScanParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScanParameters.swift; sourceTree = ""; }; + 630812602E589F5600536FE7 /* OSBARCScanResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScanResult.swift; sourceTree = ""; }; 7507FC1227FC2AAE003809F6 /* OSBarcodeLib.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OSBarcodeLib.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7507FC1A27FC2AAE003809F6 /* OSBarcodeLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OSBarcodeLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 750B35862AFA93B100F90083 /* OSBARCScannerViewConfigurationValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScannerViewConfigurationValues.swift; sourceTree = ""; }; @@ -220,6 +222,7 @@ 7513C4802B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift */, 7513C4812B03E86B005E81C4 /* OSBARCOrientationModel.swift */, 630812562E5866C700536FE7 /* OSBARCScannerHint.swift */, + 630812602E589F5600536FE7 /* OSBARCScanResult.swift */, ); path = Models; sourceTree = ""; @@ -463,6 +466,7 @@ 75562B3F2B1767C100F31AF6 /* UIApplication+Window.swift in Sources */, 75D20FE72AF17AFC009AD84D /* OSBARCCoordinatorProtocol.swift in Sources */, 75D20FE22AF16B0A009AD84D /* OSBARCManagerFactory.swift in Sources */, + 630812612E589F5A00536FE7 /* OSBARCScanResult.swift in Sources */, 75EF59A42B0E4A410084F144 /* OSBARCScanButton.swift in Sources */, 75183A182B73936500AFC687 /* OSBARCZoomSelectorView.swift in Sources */, 75D20FDC2AF16AC9009AD84D /* OSBARCManager.swift in Sources */, diff --git a/OSBarcodeLib/Manager/OSBARCManager.swift b/OSBarcodeLib/Manager/OSBARCManager.swift index deef76d..234c211 100644 --- a/OSBarcodeLib/Manager/OSBARCManager.swift +++ b/OSBarcodeLib/Manager/OSBARCManager.swift @@ -35,7 +35,7 @@ struct OSBARCManager { extension OSBARCManager: OSBARCManagerProtocol { func scanBarcode( with parameters: OSBARCScanParameters - ) async throws -> String { + ) async throws -> OSBARCScanResult { // validates if the user has access to the device's camera. let hasCameraAccess = await self.permissionsBehaviour.hasCameraAccess() if !hasCameraAccess { throw OSBARCManagerError.cameraAccessDenied } @@ -51,12 +51,12 @@ extension OSBARCManager: OSBARCManagerProtocol { /// - continuation: Object responsible for returning the method's result to its caller. private func startScanning( with parameters: OSBARCScanParameters, - continuation: CheckedContinuation + continuation: CheckedContinuation ) { DispatchQueue.main.async { - self.scannerBehaviour.startScanning(with: parameters) { scannedCode in - if !scannedCode.isEmpty { - continuation.resume(returning: scannedCode) + self.scannerBehaviour.startScanning(with: parameters) { scanResult in + if !scanResult.text.isEmpty { + continuation.resume(returning: scanResult) } else { continuation.resume(throwing: OSBARCManagerError.scanningCancelled) } diff --git a/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift b/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift index e6dceb8..b2a513c 100644 --- a/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift +++ b/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift @@ -9,5 +9,5 @@ public protocol OSBARCManagerProtocol { /// - Returns: When successful, it returns the text associated with the scanned barcode. func scanBarcode( with parameters: OSBARCScanParameters - ) async throws -> String + ) async throws -> OSBARCScanResult } diff --git a/OSBarcodeLib/Models/OSBARCScanResult.swift b/OSBarcodeLib/Models/OSBARCScanResult.swift new file mode 100644 index 0000000..16fb5af --- /dev/null +++ b/OSBarcodeLib/Models/OSBARCScanResult.swift @@ -0,0 +1,13 @@ +public struct OSBARCScanResult { + /// The actual textual data that was scanned + public let text: String + + /// The format that was scanned, or `unknown` if unable to determine + public let format: OSBARCScannerHint +} + +extension OSBARCScanResult { + static func empty() -> OSBARCScanResult { + return OSBARCScanResult(text: "", format: .unknown) + } +} diff --git a/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift b/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift index b2a9717..413f9bd 100644 --- a/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift +++ b/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift @@ -6,7 +6,7 @@ import Vision /// Class responsible for decoding the scanning output (in this case, barcodes). final class OSBARCCaptureOutputDecoder: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { /// The object containing the value to return. - @Binding private var scanResult: String + @Binding private var scanResult: OSBARCScanResult /// Indicates if scanning should be done only after a button click or automatically. private let scanThroughButton: Bool /// Indicates if scanning is enabled (when there's a Scan Button). @@ -23,7 +23,7 @@ final class OSBARCCaptureOutputDecoder: NSObject, AVCaptureVideoDataOutputSample /// - 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) { + init(_ scanResult: Binding, _ scanThroughButton: Bool, _ scanButtonEnabled: Bool = false, andHint hint: OSBARCScannerHint? = nil) { self._scanResult = scanResult self.scanThroughButton = scanThroughButton self.scanButtonEnabled = scanButtonEnabled @@ -88,7 +88,8 @@ 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) - self.scanResult = payload + let format = OSBARCScannerHint.fromVNBarcodeSymbology(bestResult.symbology) + self.scanResult = OSBARCScanResult(text: payload, format: format) } } } diff --git a/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift b/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift index 2ca2058..9e5efb6 100644 --- a/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift +++ b/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift @@ -10,6 +10,12 @@ extension OSBARCScannerHint { } } + static func fromVNBarcodeSymbology(_ symbology: VNBarcodeSymbology) -> OSBARCScannerHint { + return Self.hintMappings.first { (_, symbologies) in + symbologies.contains(symbology) + }?.key ?? .unknown + } + static let hintMappings: [OSBARCScannerHint: [VNBarcodeSymbology]] = { var result: [OSBARCScannerHint: [VNBarcodeSymbology]] = [ .qrCode: [.qr], diff --git a/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift b/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift index 45fcb5f..714b1b6 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift @@ -5,14 +5,14 @@ import SwiftUI /// Class responsible for the barcode scanner view flow. final class OSBARCScannerBehaviour: OSBARCCoordinatable, OSBARCScannerProtocol { /// A publisher value responsible for the resulting scanned value. - @Published private var scanResult: String = "" + @Published private var scanResult: OSBARCScanResult = OSBARCScanResult.empty() /// The publisher's cancellable instance collector. private var cancellables: Set = [] func startScanning( with parameters: OSBARCScanParameters, - _ completion: @escaping (String) -> Void + _ completion: @escaping (OSBARCScanResult) -> Void ) { $scanResult .dropFirst() // drops the first value - the empty string @@ -45,7 +45,7 @@ final class OSBARCScannerBehaviour: OSBARCCoordinatable, OSBARCScannerProtocol { parameters.scanOrientation, barcodeDecoder ) - guard let viewModel: OSBARCScannerViewModel = try? .init(cameraManager: captureSessionManager) else { return completion("") } + guard let viewModel: OSBARCScannerViewModel = try? .init(cameraManager: captureSessionManager) else { return completion(OSBARCScanResult.empty()) } let scannerView = OSBARCScannerView( viewModel: viewModel, scanResult: scanResultBinding, diff --git a/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift b/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift index f5f7f1e..ff66305 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift @@ -6,6 +6,6 @@ protocol OSBARCScannerProtocol { /// - completion: The value returned or empty string in case the view is closed with no code scanned. func startScanning( with parameters: OSBARCScanParameters, - _ completion: @escaping (String) -> Void + _ completion: @escaping (OSBARCScanResult) -> Void ) } diff --git a/OSBarcodeLib/Scanner/OSBARCScannerView.swift b/OSBarcodeLib/Scanner/OSBARCScannerView.swift index e9190c5..1d9f7eb 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerView.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerView.swift @@ -7,7 +7,7 @@ struct OSBARCScannerView: View { @ObservedObject var viewModel: OSBARCScannerViewModel /// The object containing the scanned value. - @Binding var scanResult: String + @Binding var scanResult: OSBARCScanResult /// Helper text to display. let instructionsText: String @@ -49,7 +49,7 @@ struct OSBARCScannerView: View { /// Cancel button. private var cancelButton: OSBARCCancelButton { .init { - scanResult = "" // cancelling translates in scanResult being empty. + scanResult = OSBARCScanResult.empty() // cancelling translates in scanResult being empty. } } diff --git a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift index 944678e..dcc249c 100644 --- a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift +++ b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift @@ -5,8 +5,8 @@ final class OSBARCScannerStub: OSBARCScannerProtocol { func startScanning( with parameters: OSBARCScanParameters, - _ completion: @escaping (String) -> Void + _ completion: @escaping (OSBARCScanResult) -> Void ) { - completion(self.scanCancelled ? "" : OSBARCScannerStubValues.scannedCode) + completion(self.scanCancelled ? OSBARCScanResult(result: "", format: .unknown) : OSBARCScanResult(result: OSBARCScannerStubValues.scannedCode, format: .qrCode)) } } From ee6a6feb6099b9f5cc1946d0151523659c7fffb5 Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Mon, 25 Aug 2025 09:59:56 +0100 Subject: [PATCH 4/9] chore(ci): Use macos-14 instead of macos-latest Because macos-14 comes with more Xcode versions. To be addressed in https://outsystemsrd.atlassian.net/browse/RMET-4424 --- .github/workflows/github_actions.yml | 2 +- .github/workflows/prepare_release.yml | 2 +- .github/workflows/release_and_publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index 94adf2f..95c71a2 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -7,7 +7,7 @@ on: jobs: test: name: Unit-Tests - runs-on: macos-latest + runs-on: macos-14 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/prepare_release.yml b/.github/workflows/prepare_release.yml index 25d1f50..70212ec 100644 --- a/.github/workflows/prepare_release.yml +++ b/.github/workflows/prepare_release.yml @@ -16,7 +16,7 @@ on: jobs: build-and-release: if: github.ref == 'refs/heads/main' - runs-on: macos-latest + runs-on: macos-14 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/release_and_publish.yml b/.github/workflows/release_and_publish.yml index c550415..92cc14a 100644 --- a/.github/workflows/release_and_publish.yml +++ b/.github/workflows/release_and_publish.yml @@ -9,7 +9,7 @@ on: jobs: post-merge: if: contains(github.event.pull_request.labels.*.name, 'release') && github.event.pull_request.merged == true - runs-on: macos-latest + runs-on: macos-14 steps: - name: Checkout Repository From 166ad74b03c5afc95ea45e64002777e069386708 Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Mon, 25 Aug 2025 10:29:05 +0100 Subject: [PATCH 5/9] chore(ci): Remove specific device requirement from unit tests --- fastlane/Fastfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 5aba85d..42bbdc8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -18,8 +18,7 @@ default_platform(:ios) platform :ios do desc "Lane to run the unit tests" lane :unit_tests do - run_tests(device: "iPhone 8", scheme: "OSBarcodeLib", - slack_url: ENV['SLACK_WEBHOOK']) + run_tests(scheme: "OSBarcodeLib", end desc "Code coverage" From b4e3fc7782dcef28e73d99298d5ecc6a8eed37ff Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Mon, 25 Aug 2025 10:36:05 +0100 Subject: [PATCH 6/9] chore(ci): fix Fastfile --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 42bbdc8..686d228 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -18,7 +18,7 @@ default_platform(:ios) platform :ios do desc "Lane to run the unit tests" lane :unit_tests do - run_tests(scheme: "OSBarcodeLib", + run_tests(scheme: "OSBarcodeLib") end desc "Code coverage" From 6a366067f7efdd1d0abca1ca31c32545b66036bf Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Mon, 25 Aug 2025 11:20:48 +0100 Subject: [PATCH 7/9] chore: fix unit tests --- OSBarcodeLib/Models/OSBARCScanResult.swift | 2 +- OSBarcodeLibTests/OSBARCManagerTests.swift | 12 ++++++++++-- OSBarcodeLibTests/OSBARCTestValues.swift | 4 +++- OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/OSBarcodeLib/Models/OSBARCScanResult.swift b/OSBarcodeLib/Models/OSBARCScanResult.swift index 16fb5af..12735a6 100644 --- a/OSBarcodeLib/Models/OSBARCScanResult.swift +++ b/OSBarcodeLib/Models/OSBARCScanResult.swift @@ -1,4 +1,4 @@ -public struct OSBARCScanResult { +public struct OSBARCScanResult: Equatable { /// The actual textual data that was scanned public let text: String diff --git a/OSBarcodeLibTests/OSBARCManagerTests.swift b/OSBarcodeLibTests/OSBARCManagerTests.swift index db6986a..2a21bfd 100644 --- a/OSBarcodeLibTests/OSBARCManagerTests.swift +++ b/OSBarcodeLibTests/OSBARCManagerTests.swift @@ -2,9 +2,17 @@ import XCTest @testable import OSBarcodeLib private extension OSBARCManager { - func scanBarcode() async throws -> String { + func scanBarcode() async throws -> OSBARCScanResult { // `instructionText`, `buttonText`, `cameraModel` and `orientationModel` are UI-related so are irrelevant for the unit tests. - try await self.scanBarcode(with: "Instruction Text", "Scan Button", .back, and: .adaptive) + try await self.scanBarcode( + with: OSBARCScanParameters( + scanInstructions: "Instruction Text", + scanButtonText: "Scan Button", + cameraDirection: .back, + scanOrientation: .adaptive, + hint: .qrCode + ) + ) } } diff --git a/OSBarcodeLibTests/OSBARCTestValues.swift b/OSBarcodeLibTests/OSBARCTestValues.swift index 5f940e3..5f6bd40 100644 --- a/OSBarcodeLibTests/OSBARCTestValues.swift +++ b/OSBarcodeLibTests/OSBARCTestValues.swift @@ -1,3 +1,5 @@ +@testable import OSBarcodeLib + struct OSBARCScannerStubValues { - static let scannedCode = "Scanned Code" + static let scannedCode = OSBARCScanResult(text: "Scanned Code", format: .qrCode) } diff --git a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift index dcc249c..566ff0f 100644 --- a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift +++ b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift @@ -7,6 +7,6 @@ final class OSBARCScannerStub: OSBARCScannerProtocol { with parameters: OSBARCScanParameters, _ completion: @escaping (OSBARCScanResult) -> Void ) { - completion(self.scanCancelled ? OSBARCScanResult(result: "", format: .unknown) : OSBARCScanResult(result: OSBARCScannerStubValues.scannedCode, format: .qrCode)) + completion(self.scanCancelled ? OSBARCScanResult(text: "", format: .unknown) : OSBARCScannerStubValues.scannedCode) } } From f8d2794541231fa06182ff2c049fd34f41b793ee Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Mon, 25 Aug 2025 11:24:00 +0100 Subject: [PATCH 8/9] chore: revert formatting --- OSBarcodeLib/Manager/OSBARCManager.swift | 9 ++------- OSBarcodeLib/Manager/OSBARCManagerProtocol.swift | 4 +--- OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift | 5 +---- OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift | 5 +---- OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift | 5 +---- 5 files changed, 6 insertions(+), 22 deletions(-) diff --git a/OSBarcodeLib/Manager/OSBARCManager.swift b/OSBarcodeLib/Manager/OSBARCManager.swift index 234c211..cdd44fd 100644 --- a/OSBarcodeLib/Manager/OSBARCManager.swift +++ b/OSBarcodeLib/Manager/OSBARCManager.swift @@ -33,9 +33,7 @@ struct OSBARCManager { /// Implementation of the `OSBARCManagerProtocol` methods. extension OSBARCManager: OSBARCManagerProtocol { - func scanBarcode( - with parameters: OSBARCScanParameters - ) async throws -> OSBARCScanResult { + func scanBarcode(with parameters: OSBARCScanParameters) async throws -> OSBARCScanResult { // validates if the user has access to the device's camera. let hasCameraAccess = await self.permissionsBehaviour.hasCameraAccess() if !hasCameraAccess { throw OSBARCManagerError.cameraAccessDenied } @@ -49,10 +47,7 @@ extension OSBARCManager: OSBARCManagerProtocol { /// - Parameters: /// - parameters: The full parameter list to configure the scanner /// - continuation: Object responsible for returning the method's result to its caller. - private func startScanning( - with parameters: OSBARCScanParameters, - continuation: CheckedContinuation - ) { + private func startScanning(with parameters: OSBARCScanParameters, continuation: CheckedContinuation) { DispatchQueue.main.async { self.scannerBehaviour.startScanning(with: parameters) { scanResult in if !scanResult.text.isEmpty { diff --git a/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift b/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift index b2a513c..18109c0 100644 --- a/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift +++ b/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift @@ -7,7 +7,5 @@ public protocol OSBARCManagerProtocol { /// - Parameters: /// - parameters: The full parameter list to configure the scanner /// - Returns: When successful, it returns the text associated with the scanned barcode. - func scanBarcode( - with parameters: OSBARCScanParameters - ) async throws -> OSBARCScanResult + func scanBarcode(with parameters: OSBARCScanParameters) async throws -> OSBARCScanResult } diff --git a/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift b/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift index 714b1b6..8749efa 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift @@ -10,10 +10,7 @@ final class OSBARCScannerBehaviour: OSBARCCoordinatable, OSBARCScannerProtocol { /// The publisher's cancellable instance collector. private var cancellables: Set = [] - func startScanning( - with parameters: OSBARCScanParameters, - _ completion: @escaping (OSBARCScanResult) -> Void - ) { + func startScanning(with parameters: OSBARCScanParameters, _ completion: @escaping (OSBARCScanResult) -> Void) { $scanResult .dropFirst() // drops the first value - the empty string .first() // only publishes the first barcode value found diff --git a/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift b/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift index ff66305..e5097ad 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift @@ -4,8 +4,5 @@ protocol OSBARCScannerProtocol { /// - Parameters: /// - parameters: The full parameter list to configure the scanner /// - completion: The value returned or empty string in case the view is closed with no code scanned. - func startScanning( - with parameters: OSBARCScanParameters, - _ completion: @escaping (OSBARCScanResult) -> Void - ) + func startScanning(with parameters: OSBARCScanParameters, _ completion: @escaping (OSBARCScanResult) -> Void) } diff --git a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift index 566ff0f..c30294d 100644 --- a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift +++ b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift @@ -3,10 +3,7 @@ final class OSBARCScannerStub: OSBARCScannerProtocol { var scanCancelled: Bool = false - func startScanning( - with parameters: OSBARCScanParameters, - _ completion: @escaping (OSBARCScanResult) -> Void - ) { + func startScanning(with parameters: OSBARCScanParameters, _ completion: @escaping (OSBARCScanResult) -> Void) { completion(self.scanCancelled ? OSBARCScanResult(text: "", format: .unknown) : OSBARCScannerStubValues.scannedCode) } } From 7324741945f09552d4ff5191645b4bcb3fc3ac9a Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Mon, 25 Aug 2025 11:26:34 +0100 Subject: [PATCH 9/9] docs: Update README and CHANGELOG References: https://outsystemsrd.atlassian.net/browse/RMET-2962 --- CHANGELOG.md | 7 +++++++ README.md | 17 +++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a27801..d724cf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +**BREAKING CHANGE**: The signature of `scanBarcode` has been updated, both input and output. + +- Add **hint** parameter to scan for specific barcode formats +- Return the format of the scanned code inside the scan result. + ## 1.1.3 - Increase scanning area (https://outsystemsrd.atlassian.net/browse/RMET-3683). diff --git a/README.md b/README.md index 054f45b..4027076 100644 --- a/README.md +++ b/README.md @@ -73,15 +73,20 @@ The library uses the following method to interact with: ### Scan Barcode ```swift -func scanBarcode(with instructionsText: String, _ buttonText: String?, _ cameraModel: OSBARCCameraModel, and orientationModel: OSBARCOrientationModel) async throws -> String +func scanBarcode(with parameters: OSBARCScanParameters) async throws -> OSBARCScanResult ``` Triggers the barcode scanner, returning asynchronously, if successful, the scanned value. In case of error, it can throw one of the following: - **cameraAccessDenied**: if camera access has not been given. - **scanningCancelled**: If scanning has been cancelled by the end-user. -The method is composed of the following input parameters: -- **instructionText**: The text to be displayed on the scanning reader view. -- **buttonText**: The text to be displayed for the scan button, if configured. `Nil` value means that the button will not be shown. -- **cameraModel**: Indicates the camera to use to gather input. It can be `back` or `front`. -- **orientationModel**: Indicates the scanning reader view orientation. It can be locked to `portrait` or `landscape` or adapted to the device's current orientation if the value is `adaptive`. +The method is composed of the following input parameters, contained inside `OSBARCScanParameters` structure: +- **scanInstructions**: The text to be displayed on the scanning reader view. +- **scanButtonText**: The text to be displayed for the scan button, if configured. `Nil` value means that the button will not be shown. +- **cameraDirection**: Indicates the camera to use to gather input. It can be `back` or `front`. +- **scanOrientation**: Indicates the scanning reader view orientation. It can be locked to `portrait` or `landscape` or adapted to the device's current orientation if the value is `adaptive`. +- **hint**: Indicates scan of a specific format (e.g. only qr code). `Nil` or `unknown` value means it can scan all. + +The method returns a `OSBARCScanResult structure`, containing: +- **text**: The actual textual data that was scanned. +- **format**: The format that was scanned, or `unknown` if unable to determine.