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 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/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/OSBarcodeLib.xcodeproj/project.pbxproj b/OSBarcodeLib.xcodeproj/project.pbxproj index ee06ac1..15f79e1 100644 --- a/OSBarcodeLib.xcodeproj/project.pbxproj +++ b/OSBarcodeLib.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ 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 */; }; + 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 */; }; @@ -70,6 +74,10 @@ /* 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 = ""; }; + 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 = ""; }; @@ -208,10 +216,13 @@ 758E6C142B0238F100FC16D9 /* Models */ = { isa = PBXGroup; children = ( + 6308125E2E5891E700536FE7 /* OSBARCScanParameters.swift */, 7513C4822B03E86B005E81C4 /* MappableProtocol */, 758E6C152B0238FF00FC16D9 /* OSBARCCameraModel.swift */, 7513C4802B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift */, 7513C4812B03E86B005E81C4 /* OSBARCOrientationModel.swift */, + 630812562E5866C700536FE7 /* OSBARCScannerHint.swift */, + 630812602E589F5600536FE7 /* OSBARCScanResult.swift */, ); path = Models; sourceTree = ""; @@ -219,6 +230,7 @@ 758E6C172B0239C100FC16D9 /* Extensions */ = { isa = PBXGroup; children = ( + 6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */, 758E6C182B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift */, 7513C48F2B03E922005E81C4 /* AVCaptureVideoOrientation+CustomInit.swift */, 75183A152B7389EC00AFC687 /* Float+DecimalPlacesCleaner.swift */, @@ -436,6 +448,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 */, @@ -453,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 */, @@ -474,7 +488,9 @@ 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 */, 75EF599A2B0E2F220084F144 /* OSBARCBackgroundView.swift in Sources */, 7513C4852B03E86B005E81C4 /* OSBARCDeviceTypeModel.swift in Sources */, diff --git a/OSBarcodeLib/Manager/OSBARCManager.swift b/OSBarcodeLib/Manager/OSBARCManager.swift index eee03cb..cdd44fd 100644 --- a/OSBarcodeLib/Manager/OSBARCManager.swift +++ b/OSBarcodeLib/Manager/OSBARCManager.swift @@ -33,34 +33,25 @@ 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 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 } // 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: 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. + /// - 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, - continuation: CheckedContinuation - ) { + private func startScanning(with parameters: OSBARCScanParameters, continuation: CheckedContinuation) { DispatchQueue.main.async { - self.scannerBehaviour.startScanning(with: instructionsText, buttonText, cameraModel, and: orientationModel) { 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 53490f3..18109c0 100644 --- a/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift +++ b/OSBarcodeLib/Manager/OSBARCManagerProtocol.swift @@ -5,10 +5,7 @@ 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. + /// - 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) async throws -> String + func scanBarcode(with parameters: OSBARCScanParameters) async throws -> OSBARCScanResult } 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/Models/OSBARCScanResult.swift b/OSBarcodeLib/Models/OSBARCScanResult.swift new file mode 100644 index 0000000..12735a6 --- /dev/null +++ b/OSBarcodeLib/Models/OSBARCScanResult.swift @@ -0,0 +1,13 @@ +public struct OSBARCScanResult: Equatable { + /// 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/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..413f9bd 100644 --- a/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift +++ b/OSBarcodeLib/Scanner/CameraManager/OSBARCCaptureOutputDecoder.swift @@ -6,33 +6,28 @@ 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). 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 }() @@ -93,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 new file mode 100644 index 0000000..9e5efb6 --- /dev/null +++ b/OSBarcodeLib/Scanner/Extensions/OSBARCScannerHint+VNBarcodeSymbology.swift @@ -0,0 +1,55 @@ +import Foundation +import Vision + +extension OSBARCScannerHint { + func toVNBarcodeSymbologies() -> [VNBarcodeSymbology] { + if let specificSymbiology = self.toVNBarcodeSymbology() { + return specificSymbiology + } else { + return self.allBarcodeTypes + } + } + + 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], + .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..8749efa 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerBehaviour.swift @@ -5,12 +5,12 @@ 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 instructionsText: String, _ buttonText: String?, _ cameraModel: OSBARCCameraModel, and orientationModel: OSBARCOrientationModel, _ completion: @escaping (String) -> 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 @@ -29,28 +29,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 + shouldShowButton, + andHint: parameters.hint ) let captureSessionManager = OSBARCCaptureSessionManager( - cameraModel, - orientationModel, + parameters.cameraDirection, + 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, - 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 53dc6c1..e5097ad 100644 --- a/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift +++ b/OSBarcodeLib/Scanner/OSBARCScannerProtocol.swift @@ -2,10 +2,7 @@ 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. + /// - 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, _ completion: @escaping (String) -> Void) + func startScanning(with parameters: OSBARCScanParameters, _ 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/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 b6237b2..c30294d 100644 --- a/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift +++ b/OSBarcodeLibTests/Stubs/OSBARCScannerStub.swift @@ -3,13 +3,7 @@ final class OSBARCScannerStub: OSBARCScannerProtocol { var scanCancelled: Bool = false - func startScanning( - with instructionsText: String, - _ buttonText: String?, - _ cameraModel: OSBARCCameraModel, - and orientationModel: OSBARCOrientationModel, - _ completion: @escaping (String) -> Void - ) { - completion(self.scanCancelled ? "" : OSBARCScannerStubValues.scannedCode) + func startScanning(with parameters: OSBARCScanParameters, _ completion: @escaping (OSBARCScanResult) -> Void) { + completion(self.scanCancelled ? OSBARCScanResult(text: "", format: .unknown) : OSBARCScannerStubValues.scannedCode) } } 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. diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 5aba85d..686d228 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"