Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/github_actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/prepare_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,5 @@ iOSInjectionProject/
.swiftlint.yml

scripts/build/
.DS_Store
build/
.DS_Store
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
16 changes: 16 additions & 0 deletions OSBarcodeLib.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -70,6 +74,10 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
630812562E5866C700536FE7 /* OSBARCScannerHint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScannerHint.swift; sourceTree = "<group>"; };
6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSBARCScannerHint+VNBarcodeSymbology.swift"; sourceTree = "<group>"; };
6308125E2E5891E700536FE7 /* OSBARCScanParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScanParameters.swift; sourceTree = "<group>"; };
630812602E589F5600536FE7 /* OSBARCScanResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBARCScanResult.swift; sourceTree = "<group>"; };
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 = "<group>"; };
Expand Down Expand Up @@ -208,17 +216,21 @@
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 = "<group>";
};
758E6C172B0239C100FC16D9 /* Extensions */ = {
isa = PBXGroup;
children = (
6308125C2E58902100536FE7 /* OSBARCScannerHint+VNBarcodeSymbology.swift */,
758E6C182B0239E700FC16D9 /* AVCaptureDevice+OSBARCModelMappable.swift */,
7513C48F2B03E922005E81C4 /* AVCaptureVideoOrientation+CustomInit.swift */,
75183A152B7389EC00AFC687 /* Float+DecimalPlacesCleaner.swift */,
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand All @@ -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 */,
Expand Down
23 changes: 7 additions & 16 deletions OSBarcodeLib/Manager/OSBARCManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, any Error>
) {
private func startScanning(with parameters: OSBARCScanParameters, continuation: CheckedContinuation<OSBARCScanResult, any Error>) {
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)
}
Expand Down
7 changes: 2 additions & 5 deletions OSBarcodeLib/Manager/OSBARCManagerProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
28 changes: 28 additions & 0 deletions OSBarcodeLib/Models/OSBARCScanParameters.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
13 changes: 13 additions & 0 deletions OSBarcodeLib/Models/OSBARCScanResult.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
20 changes: 20 additions & 0 deletions OSBarcodeLib/Models/OSBARCScannerHint.swift
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<AnyCancellable> = []

/// 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<String>, _ 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<OSBARCScanResult>, _ 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
Expand Down Expand Up @@ -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
}()
Expand All @@ -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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading
Loading