Deprecation warning

Please note that this is outdated documentation for an older release of the Scandit Barcode Scanner SDK.

We are deprecating the 5.x API on all platforms (except Linux). Release 5.19 in April 2021 will be our final. Applications running 5.x will continue to work, and we will continue to release critical bug fixes and security patches only, for one year. We encourage you to migrate to 6.x and take advantage of our latest / advanced features and improved performance.

You'll find the updated documentation at: Data Capture SDK Documentation for iOS

Matrix Scan/Scanning Multiple Barcodes

Motivation

Sometimes you have packages/pallets with several codes, with the Scandit Barcode Scanner you can scan all the codes or just a subset at the same time, saving time and money. To do this you have to use the Matrix Scan option.

Matrix Scan

Matrix Scan track recognized barcodes over time by making it easier to use.

Enabling Matrix Scan

To implement Matrix Scanning

One of the use cases of Matrix Scan is to detect when the specified number of expected codes has been decoded by the scanner. To implement this scenario:

  • In the barcodePicker:didScan: (SBSScanDelegate-p) method, wait until the number of expected codes have been decoded, then store the codes and pause/stop the session. If you pause and plan to resume but want to start a new session, make sure that you clear the session first (see code snippet below).

At this point you can start the barcode picker and any recognized barcodes of the enabled symbologies will be highlighted by a filled green rectangle. Barcodes that have been localized but not recognized will be highlighted by a green border.

Objective-C:

- (void)setupScanner {
// Enable the relevant symbologies.
[scanSettings setSymbology:SBSSymbologyEAN13 enabled:YES];
[scanSettings setSymbology:SBSSymbologyCode39 enabled:YES];
// Enable Matrix Scanning.
scanSettings.matrixScanEnabled = YES;
// The maximum number of codes to be decoded every frame.
scanSettings.maxNumberOfCodesPerFrame = 3;
// Create the picker, set the listener, add the picker to the view hierarchy and start it.
SBSBarcodePicker *picker = [[SBSBarcodePicker alloc] initWithSettings:scanSettings];
picker.scanDelegate = self;
// Set the GUI style accordingly.
[self addChildViewController:picker];
picker.view.frame = self.view.bounds;
[self.view addSubview:picker.view];
[picker didMoveToParentViewController:self];
[picker startScanning];
}
- (void)barcodePicker:(SBSBarcodePicker *)picker didScan:(SBSScanSession *)session {
// Number of expected barcodes.
int numExpectedCodes = 3;
// Get all the scanned barcodes from the session.
NSArray *allCodes = session.allRecognizedCodes;
// If the number of scanned codes is greater than or equal to the number of expected barcodes,
// pause the scanning and clear the session (to remove recognized barcodes).
if ([allCodes count] >= numExpectedCodes) {
// Stop scanning or pause and clear the session.
[session stopScanning];
// ...
}
}

Swift:

func setupScanner() {
let scanSettings = SBSScanSettings.default()
// Enable the relevant symbologies.
scanSettings.setSymbology(.ean13, enabled: true)
scanSettings.setSymbology(.code39, enabled: true)
// Enable Matrix Scanning.
scanSettings.isMatrixScanEnabled = true
// The maximum number of codes to be decoded every frame.
scanSettings.maxNumberOfCodesPerFrame = 3
// Create the picker, set the listener, add the picker to the view hierarchy and start it.
let picker = SBSBarcodePicker(settings: scanSettings)
picker.scanDelegate = self
// Set the GUI style accordingly.
picker.overlayController.guiStyle = .matrixScan;
addChildViewController(picker)
picker.view.frame = view.bounds
view.addSubview(picker.view)
picker.didMove(toParentViewController: self)
picker.startScanning()
}
func barcodePicker(_ picker: SBSBarcodePicker, didScan session: SBSScanSession) {
// Number of expected barcodes.
let numExpectedCodes = 3
// Get all the scanned barcodes from the session.
let allCodes = session.allRecognizedCodes
// If the number of scanned codes is greater than or equal to the number of expected barcodes,
// pause the scanning and clear the session (to remove recognized barcodes).
if allCodes.count >= numExpectedCodes {
// Stop scanning or pause and clear the session.
session.stopScanning()
// ...
}
}

Rejecting unwanted codes

Like normal scanning Matrix Scanning provides you the option to reject codes. Just like for normal scanning rejected codes are not added to the scan session and do not provide any feedback like vibrating or beeping. In Matrix Scan the rejected codes are still drawn on the screen but in a different color. This gives the user visual feedback that a certain code is not the one you are looking for. To implement rejection

Note: Rejecting in the barcodePicker:didScan: (SBSScanDelegate-p) method is not allowed.

Objective-C:

- (void)setupScanner {
// Same initialization as above.
// Set the process frame listener.
picker.processFrameDelegate = self;
}
- (void)barcodePicker:(SBSBarcodePicker *)aPicker didProcessFrame:(CMSampleBufferRef)frame session:(SBSScanSession *)session {
for (NSNumber *identifier in session.trackedCodes) {
SBSTrackedCode *trackedCode = session.trackedCodes[identifier];
// Reject all Code39 barcodes.
if (trackedCode.symbology == SBSSymbologyCode39) {
[session rejectTrackedCode:trackedCode];
}
}
}

Swift:

func setupScanner() {
// Same initialization as above.
// Set the process frame listener.
picker.processFrameDelegate = self
}
func barcodePicker(_ barcodePicker: SBSBarcodePicker, didProcessFrame frame: CMSampleBuffer, session: SBSScanSession) {
guard let trackedCodes = session.trackedCodes else { return }
for (_, trackedCode) in trackedCodes {
if trackedCode.symbology == .code39 {
session.rejectTrackedCode(trackedCode)
}
}
}

Providing your own visualization

Matrix Scanning returns the locations of tracked barcodes with every processed frame. This allows you to replace the default visualization with your own. For example, you might want to overlay the barcodes with images of the products they refer to. To add your own visualization

  • Set a SBSProcessFrameDelegate on the picker SBSBarcodePicker::processFrameDelegate.
  • In the barcodePicker:didProcessFrame:session: (SBSProcessFrameDelegate-p) method you fetch the currently tracked barcodes from the session through rejectTrackedCode: (SBSScanSession). The object is a dictionary with keys of type NSNumber which identify the SBSTrackedCodes over different frames.
  • Compare the keys of the current frame with the one from the previous one and change your visualization accordingly. Instead of using SBSTrackedCode::location which gives you the exact location of the barcode in the frame, you should generally use SBSTrackedCode::predictedLocation which gives you a predicted location for the barcode. Using the predicted location avoids lagging behind the camera feed that is displayed.
  • There are some state transitions where animating from the previous location of a tracked barcode to its current position can end up with weird artifacts as the edge ordering of the location is not stable. If your visualization depends on the edge ordering make sure that you query SBSTrackedCode::shouldAnimateFromPreviousToNextState before animating. The default visualization makes use of this as it draws the location with all four edges and therefore depends on the edge ordering, if the order of the edges were to change it is possible that an animation would flip the visualization which is not what happens to the actual barcode. However, a visualization that just draws something in the center of the location does not depend on the edge ordering and can ignore SBSTrackedCode::shouldAnimateFromPreviousToNextState.
  • When animating your visualization take into consideration SBSTrackedCode::deltaTimeForPrediction which tells you how long it will take the code to move to the predicted location. This time generally is about as long as it will take the barcode picker to process the next frame, giving you a new predicted location right as your previous animation is coming to an end.

Important: The coordinates you get back from SBSTrackedCode::location and SBSTrackedCode::predictedLocation are in the coordinate system of the processed frame. To visualize them on top of the picker's camera feed you have to convert them to the picker's coordinate system which you do by calling SBSBarcodePicker::convertPointToPickerCoordinates.

Important: Don't forget that the barcodePicker:didProcessFrame:session: (SBSProcessFrameDelegate-p) method is not called on the mainThread, you have to switch to it to change the UI.

Objective-C:

- (instancetype)init {
self = [super init];
if (self) {
// visualizations is a property of type NSMutableDictionary<NSNumber *, CAShapeLayer *> *
_visualizations = [NSMutableDictionary dictionary];
}
}
- (void)barcodePicker:(SBSBarcodePicker *)aPicker didProcessFrame:(CMSampleBufferRef)frame session:(SBSScanSession *)session {
// Iterate over the current visualizations and remove all that are no longer tracked.
NSDictionary<NSNumber *, CAShapeLayer *> *currentVisualizations = [self.visualizations copy];
for (NSNumber *identifier in currentVisualizations) {
if (session.trackedCodes[identifier] == nil) {
self.visualizations[identifier] = nil;
// Maybe add an animation for barcodes that vanish.
}
}
// Iterate over all the currently tracked barcodes, update old ones and add new ones.
for (NSNumber *identifier in session.trackedCodes) {
if (self.visualizations[identifier] != nil) {
// Update the visualization
SBSTrackedCode *trackedCode = session.trackedCodes[identifier];
// Animate to the new predicted location.
}
} else {
// Add a new visualization, maybe animate the appearance.
}
}
}

Swift:

// Dictionary accessed from the session thread
var visualizations: [NSNumber: CAShapeLayer] = [:]
func barcodePicker(_ barcodePicker: SBSBarcodePicker, didProcessFrame frame: CMSampleBuffer, session: SBSScanSession) {
guard let trackedCodes = session.trackedCodes else { return }
// Iterate over the current visualizations and remove all that are no longer tracked.
let currentVisualizations = visualizations
for (identifier, _) in currentVisualizations {
if trackedCodes[identifier] == nil {
self.visualizations[identifier] = nil
// Maybe add an animation for barcodes that vanish.
}
}
// Iterate over all the currently tracked barcodes, update old ones and add new ones.
for (identifier, trackedCode) in trackedCodes {
if self.visualizations[identifier] != nil {
// Update the visualization
if trackedCode.shouldAnimateFromPreviousToNextState {
// Animate to the new predicted location.
}
} else {
// Add a new visualization, maybe animate the appearance.
}
}
}