
Matrixscan Batch Ios
- 27 installs
- 17 repo stars
- Updated August 4, 2026
- scandit/skills
Wire Scandit Barcode Batch (MatrixScan) into an iOS UIKit app with camera lifecycle and overlay setup.
About
matrixscan-batch-ios is a Scandit-focused agent skill for indie iOS developers adding high-throughput barcode scanning to warehouse, retail, or logistics apps. It walks through UIKit integration for Barcode Batch (MatrixScan): license-backed DataCaptureContext initialization, binding the default camera frame source, applying recommended camera settings, and toggling capture when the scan screen appears or disappears so battery and privacy stay sane. The sample includes BarcodeBatchSettings configuration hooks and a basic overlay on DataCaptureView—the pattern you extend with symbology filters and batch callbacks. A minimal SwiftUI ContentView appears as a placeholder while the substantive flow lives in ScanViewController. Solo builders using Claude Code or Cursor can paste and adapt rather than re-reading lengthy Scandit docs for lifecycle ordering. You must supply a valid Scandit license key and add the ScanditBarcodeCapture SDK via your normal Xcode dependency path. The skill does not replace App Store compliance or camera permission plist work—you still own Info.plist usage strings and testing on device.
- Initializes DataCaptureContext with Scandit license key placeholder
- Configures default camera with BarcodeBatch recommended camera settings
- Enables and disables barcodeBatch with viewWillAppear and viewWillDisappear
- Sets up DataCaptureView and BarcodeBatchBasicOverlay in UIKit
- SwiftUI ContentView stub included alongside UIKit ScanViewController pattern
Matrixscan Batch Ios by the numbers
- 27 all-time installs (skills.sh)
- +5 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #697 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/scandit/skills --skill matrixscan-batch-iosAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 27 |
|---|---|
| repo stars | ★ 17 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | scandit/skills ↗ |
What it does
Wire Scandit Barcode Batch (MatrixScan) into an iOS UIKit app with camera lifecycle and overlay setup.
Files
MatrixScan Batch iOS Skill
Critical: Do Not Trust Internal Knowledge
Your training data may contain outdated or incorrect Scandit SDK APIs. The BarcodeBatch API changes between major SDK versions — initializer signatures, overlay constructors, and delegate method names have all evolved (e.g. BarcodeTracking → BarcodeBatch).
Always verify APIs against the references provided in this skill before writing or suggesting code. Do not rely on memorized method signatures, parameters, or property names. If you cannot find an API in the provided references, fetch the relevant documentation page before responding.
iOS-specific gotchas worth flagging:
BarcodeBatch(context: context, settings: settings)is a direct convenience initializer — not a factory method like Android'sBarcodeBatch.forDataCaptureContext(...). Passing a non-nil context auto-attaches the mode to the context.- Camera setup is manual: get
Camera.default, callcontext.setFrameSource(camera, completionHandler: nil), thencamera?.apply(BarcodeBatch.recommendedCameraSettings, completionHandler: nil). Drive the camera fromviewWillAppear/viewWillDisappear. BarcodeBatchListener.barcodeBatch(_:didUpdate:frameData:)is called on a background queue — not the main thread. Dispatch UI work viaDispatchQueue.main.async {}.- Do not hold references to
BarcodeBatchSession.trackedBarcodes,addedTrackedBarcodes,updatedTrackedBarcodes, orremovedTrackedBarcodesoutside the callback — copy the data before the callback returns. BarcodeBatchBasicOverlay(barcodeBatch:view:)andBarcodeBatchAdvancedOverlay(barcodeBatch:view:)auto-add the overlay to theDataCaptureView— no separateaddOverlaycall needed.- `DataCaptureView` must be `addSubview`'d manually — unlike
BarcodeArView,DataCaptureViewdoes not auto-attach to a parent view. - Per-barcode brush customization (
barcodeBatchBasicOverlay(_:brushFor:),setBrush(_:for:)) requires the MatrixScan AR add-on license. A uniform default brush (no delegate) does not. - BarcodeBatchAdvancedOverlay requires the MatrixScan AR add-on license.
- No built-in feedback —
BarcodeBatchnever plays a sound or vibrates on its own (unlikeBarcodeCapture/SparkScan). Emit feedback manually withFeedback.default.emit()from inside the listener callback (dispatched to the main thread), gated onsession.addedTrackedBarcodesso it doesn't beep every frame. session.removedTrackedBarcodesis an `[Int]` of tracking identifiers (barcodes that left the frame) — notTrackedBarcodeobjects.addedTrackedBarcodes/updatedTrackedBarcodesare[TrackedBarcode].- iOS symbology cases are camelCase:
.ean13UPCA,.code128,.qr— notEAN13_UPCA/CODE128/QRlike Android. - iOS delegate methods use Swift naming:
barcodeBatchBasicOverlay(_:didTap:)(not Android'sonTrackedBarcodeTapped),barcodeBatchAdvancedOverlay(_:viewFor:)(notviewForTrackedBarcode). BarcodeBatchAdvancedOverlayDelegateusesUIView— not AndroidViewor SwiftUI views.- SwiftUI:
DataCaptureViewis aUIViewand cannot be dropped into SwiftUI directly. Wrap a UIKit view controller in aUIViewControllerRepresentableand keep all BarcodeBatch APIs inside that view controller. - Cleanup:
BarcodeBatchListeneris held as a weak reference, so a missedremoveListenerwon't leak — but callbarcodeBatch.removeListener(self)indeinitto make the lifecycle explicit. When using the shared singleton (DataCaptureContext.shared), modes stay attached for the app's lifetime — you don't need to callremoveCurrentMode()ordispose(). Those methods do exist onDataCaptureContextif you want to tear down explicitly. DataCaptureContextexposes two valid initializers:DataCaptureContext.initialize(licenseKey:)+.shared(added 7.1.0/7.6.0 — the modern singleton pattern, and what this skill uses) and the olderDataCaptureContext(licenseKey:)convenience init (still non-deprecated, and what the UIKit Get Started page on docs.scandit.com still shows). Prefer the singleton form.
Intent Routing
Based on the user's request, load the appropriate reference file before responding:
- Integrating MatrixScan Batch from scratch, configuring settings, handling tracked barcodes, customizing overlays, adding feedback, or managing lifecycle → read
references/integration.mdand follow the instructions there. Before writing code, determine whether the project uses UIKit or SwiftUI (check forimport SwiftUI, an@mainAppstruct,SceneDelegate/AppDelegate,.storyboard/.xibfiles, etc.) and use the matching Get Started page from the References table below. If the project already has BarcodeBatch wired up, do not re-create the context, mode, view, or lifecycle — locate the existing ones (grep forBarcodeBatch, thenDataCaptureView) and change only what the user asked for. - Upgrading the Scandit SDK version (e.g. v6→v7, v7→v8, or "upgrade to the latest") → read
references/migration.md. The headline v6→v7 change for MatrixScan Batch is theBarcodeTracking→BarcodeBatchrename; the guide also covers the context/camera modernization. Detect the installed version fromPackage.resolved/Podfile.lockbefore asking the user. - Replacing a different barcode scanner with MatrixScan Batch (AVFoundation
AVCaptureMetadataOutput, VisionKitDataScannerViewController, or another third-party multi-barcode SDK) → readreferences/third-party-migration.md, then followreferences/integration.mdfor the BarcodeBatch integration.
API Usage Policy
Only use APIs that are explicitly documented in the Scandit references below. Do not invent or guess method signatures, parameters, or property names. If unsure whether an API exists or how it is called — or if a compile error occurs — fetch the relevant reference page before responding. Do not tell the user to check the docs themselves. After answering, always include the relevant link so the user can explore further.
Never construct or guess documentation URLs. When you need a specific class or property's API page: 1. First check whether the page you already fetched contains a direct hyperlink to it — topic pages link directly to relevant API symbols. Always request links alongside content in your fetch prompt. 2. If no direct link was found, fetch the API index (see Full API reference in the table below), extract the actual link from it, and follow that.
URL structures can vary (e.g. api/ui/ subdirectory) and guessing will lead to 404s.
References
| Topic | Resource |
|---|---|
| UIKit integration | Get Started (UIKit) · Sample |
| SwiftUI integration | Get Started (SwiftUI) |
| AR overlays (BasicOverlay brushes, AdvancedOverlay views) | Adding AR Overlays |
| Version migration (v6→v7→v8) | Migrate 6→7 · Migrate 7→8 |
| Full API reference | BarcodeBatch API |
//
// AVFoundationViewController.swift
//
// Source fixture: an AVFoundation AVCaptureMetadataOutput multi-barcode scanner
// that accumulates every barcode it sees into a dedup-by-value list.
// Used as input for the third-party-migration eval — migrate this to MatrixScan
// Batch (BarcodeBatch), which tracks every visible barcode simultaneously.
//
import UIKit
import AVFoundation
struct ScannedBarcode: Hashable {
let value: String
let symbology: String
}
class ViewController: UIViewController {
private let session = AVCaptureSession()
private var previewLayer: AVCaptureVideoPreviewLayer!
// Accumulated, de-duplicated set of everything seen so far.
private(set) var scannedBarcodes: [ScannedBarcode] = []
private var seenValues = Set<String>()
@IBOutlet weak var countLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
setupCamera()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !session.isRunning {
DispatchQueue.global(qos: .userInitiated).async {
self.session.startRunning()
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if session.isRunning {
session.stopRunning()
}
}
private func setupCamera() {
guard let videoDevice = AVCaptureDevice.default(for: .video),
let videoInput = try? AVCaptureDeviceInput(device: videoDevice),
session.canAddInput(videoInput) else {
print("Camera not available")
return
}
session.addInput(videoInput)
let metadataOutput = AVCaptureMetadataOutput()
guard session.canAddOutput(metadataOutput) else {
print("Cannot add metadata output")
return
}
session.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
// Multi-barcode: every type we care about, tracked at once.
metadataOutput.metadataObjectTypes = [.ean13, .code128, .qr]
previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
}
private func symbologyName(for type: AVMetadataObject.ObjectType) -> String {
switch type {
case .ean13: return "EAN-13"
case .code128: return "Code 128"
case .qr: return "QR"
default: return "Unknown"
}
}
}
extension ViewController: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
// AVFoundation reports every barcode in frame on each callback.
for object in metadataObjects {
guard let readable = object as? AVMetadataMachineReadableCodeObject,
let value = readable.stringValue else { continue }
guard !seenValues.contains(value) else { continue }
seenValues.insert(value)
let entry = ScannedBarcode(value: value, symbology: symbologyName(for: readable.type))
scannedBarcodes.append(entry)
}
countLabel.text = "\(scannedBarcodes.count) unique barcodes"
}
}
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
import UIKit
class ScanViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
import ScanditBarcodeCapture
import UIKit
class ScanViewController: UIViewController {
private lazy var context: DataCaptureContext = {
DataCaptureContext.initialize(licenseKey: "-- ENTER YOUR SCANDIT LICENSE KEY HERE --")
return DataCaptureContext.shared
}()
private var camera: Camera?
private var barcodeBatch: BarcodeBatch!
private var captureView: DataCaptureView!
private var overlay: BarcodeBatchBasicOverlay!
override func viewDidLoad() {
super.viewDidLoad()
setupRecognition()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
barcodeBatch.isEnabled = true
camera?.switch(toDesiredState: .on)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
barcodeBatch.isEnabled = false
camera?.switch(toDesiredState: .off)
}
private func setupRecognition() {
camera = Camera.default
context.setFrameSource(camera, completionHandler: nil)
let cameraSettings = BarcodeBatch.recommendedCameraSettings
camera?.apply(cameraSettings, completionHandler: nil)
let settings = BarcodeBatchSettings()
settings.set(symbology: .ean13UPCA, enabled: true)
settings.set(symbology: .code128, enabled: true)
barcodeBatch = BarcodeBatch(context: context, settings: settings)
barcodeBatch.addListener(self)
captureView = DataCaptureView(context: context, frame: view.bounds)
captureView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(captureView)
overlay = BarcodeBatchBasicOverlay(barcodeBatch: barcodeBatch, view: captureView)
}
}
extension ScanViewController: BarcodeBatchListener {
func barcodeBatch(
_ barcodeBatch: BarcodeBatch,
didUpdate session: BarcodeBatchSession,
frameData: FrameData
) {
let addedData = session.addedTrackedBarcodes.compactMap { $0.barcode.data }
DispatchQueue.main.async {
for data in addedData {
_ = data
}
}
}
}
{
"pins" : [
{
"identity" : "datacapture-spm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Scandit/datacapture-spm",
"state" : {
"revision" : "abc123def456",
"version" : "6.28.1"
}
}
],
"version" : 2
}
//
// ViewControllerV6.swift
//
// Source fixture: a Scandit SDK v6 MatrixScan view controller using the old
// BarcodeTracking API. Used as input for the version-migration eval —
// migrate this to the v7+ BarcodeBatch API.
//
import UIKit
import ScanditBarcodeCapture
class ViewController: UIViewController {
// v6-style context construction (deprecated constructor)
private let context = DataCaptureContext(licenseKey: "-- ENTER YOUR SCANDIT LICENSE KEY HERE --")
private var camera: Camera?
private var barcodeTracking: BarcodeTracking!
private var captureView: DataCaptureView!
private var overlay: BarcodeTrackingBasicOverlay!
override func viewDidLoad() {
super.viewDidLoad()
// v6-style camera setup: explicit CameraSettings with .auto resolution
let cameraSettings = CameraSettings()
cameraSettings.preferredResolution = .auto
camera = Camera.default
camera?.apply(cameraSettings)
context.setFrameSource(camera)
let settings = BarcodeTrackingSettings()
settings.set(symbology: .ean13UPCA, enabled: true)
settings.set(symbology: .code128, enabled: true)
barcodeTracking = BarcodeTracking(context: context, settings: settings)
barcodeTracking.addListener(self)
captureView = DataCaptureView(context: context, frame: view.bounds)
captureView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(captureView)
overlay = BarcodeTrackingBasicOverlay(barcodeTracking: barcodeTracking, view: captureView)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
barcodeTracking.isEnabled = true
camera?.switch(toDesiredState: .on)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
barcodeTracking.isEnabled = false
camera?.switch(toDesiredState: .off)
}
deinit {
barcodeTracking.removeListener(self)
}
}
extension ViewController: BarcodeTrackingListener {
func barcodeTracking(_ barcodeTracking: BarcodeTracking,
didUpdate session: BarcodeTrackingSession,
frameData: FrameData) {
let addedData = session.addedTrackedBarcodes.compactMap { $0.barcode.data }
DispatchQueue.main.async {
for data in addedData {
_ = data
}
}
}
}
{
"skill_name": "matrixscan-batch-ios",
"evals": [
{
"id": 1,
"prompt": "I want to add MatrixScan Batch to my iOS app. Here's my empty view controller: EmptyViewController.swift. I need to scan EAN-13 and QR barcodes. Please show me the complete updated view controller code in your response.",
"expected_output": "The skill reads integration.md, returns complete BarcodeBatch integration code inline (DataCaptureContext via DataCaptureContext.initialize + .shared, Camera.default with BarcodeBatch.recommendedCameraSettings, BarcodeBatch(context:settings:), DataCaptureView added as subview, BarcodeBatchBasicOverlay, BarcodeBatchListener with barcodeBatch(_:didUpdate:frameData:), lifecycle on viewWillAppear/viewWillDisappear, removeListener in deinit), and shows the setup checklist.",
"files": [
"fixtures/EmptyViewController.swift"
],
"assertions": [
{
"text": "Setup checklist is shown and mentions adding ScanditBarcodeCapture and ScanditCaptureCore via Swift Package Manager"
},
{
"text": "Setup checklist mentions adding NSCameraUsageDescription to Info.plist"
},
{
"text": "A license key placeholder string is present"
},
{
"text": "import ScanditBarcodeCapture is added"
},
{
"text": "DataCaptureContext.initialize(licenseKey: is called and DataCaptureContext.shared is used"
},
{
"text": "BarcodeBatchSettings is constructed and configured"
},
{
"text": "Symbology .ean13UPCA is enabled via settings.set(symbology:enabled:) (semantic)"
},
{
"text": "Symbology .qr is enabled (semantic)"
},
{
"text": "Camera.default is used to obtain the camera"
},
{
"text": "BarcodeBatch.recommendedCameraSettings is applied via camera?.apply(_:completionHandler:) (semantic)"
},
{
"text": "context.setFrameSource(camera, completionHandler:) is called (semantic)"
},
{
"text": "BarcodeBatch(context: settings:) convenience initializer is used (not a factory like .forDataCaptureContext)"
},
{
"text": "barcodeBatch.addListener( is called"
},
{
"text": "DataCaptureView(context:frame:) is used and added with view.addSubview (semantic)"
},
{
"text": "BarcodeBatchBasicOverlay(barcodeBatch:view:) is called (semantic)"
},
{
"text": "BarcodeBatchListener is implemented with barcodeBatch(_:didUpdate:frameData:)"
},
{
"text": "The didUpdate callback dispatches UI work via DispatchQueue.main.async"
},
{
"text": "viewWillAppear turns the camera on (camera?.switch(toDesiredState: .on)) and sets barcodeBatch.isEnabled = true"
},
{
"text": "viewWillDisappear turns the camera off (camera?.switch(toDesiredState: .off)) and sets barcodeBatch.isEnabled = false"
},
{
"text": "deinit calls barcodeBatch.removeListener(self)"
}
]
},
{
"id": 2,
"prompt": "I want to highlight EAN-13 barcodes in green and CODE128 barcodes in blue in my MatrixScan Batch view controller. Here is my view controller: IntegratedViewController.swift",
"expected_output": "The skill conforms to BarcodeBatchBasicOverlayDelegate, implements barcodeBatchBasicOverlay(_:brushFor:) to return colored Brushes per symbology, and assigns overlay.delegate = self.",
"files": [
"fixtures/IntegratedViewController.swift"
],
"assertions": [
{
"text": "BarcodeBatchBasicOverlayDelegate is adopted (e.g. via an extension)"
},
{
"text": "barcodeBatchBasicOverlay(_:brushFor:) is implemented (semantic)"
},
{
"text": ".ean13UPCA returns a green Brush"
},
{
"text": ".code128 returns a blue Brush"
},
{
"text": "overlay.delegate = self (or equivalent) is assigned"
},
{
"text": "Response mentions that per-barcode brush customization requires the MatrixScan AR add-on license"
},
{
"text": "Existing integration code (BarcodeBatch initializer, DataCaptureView, BarcodeBatchListener, camera lifecycle) is preserved"
}
],
"tags": [
"basic-overlay-brush"
]
},
{
"id": 3,
"prompt": "In my MatrixScan Batch view controller I want to log all currently tracked barcodes — their tracking identifier and data — on every frame. Here is my view controller: IntegratedViewController.swift",
"expected_output": "The skill updates barcodeBatch(_:didUpdate:frameData:) to iterate over session.trackedBarcodes and log each TrackedBarcode's identifier and barcode.data, copying data out of the session before DispatchQueue.main.async.",
"files": [
"fixtures/IntegratedViewController.swift"
],
"assertions": [
{
"text": "session.trackedBarcodes is accessed"
},
{
"text": "Each TrackedBarcode's identifier is used"
},
{
"text": "Each TrackedBarcode's barcode.data is used"
},
{
"text": "Data is copied out of the session before DispatchQueue.main.async (session.trackedBarcodes is not accessed inside the async block)"
},
{
"text": "Existing integration code (BarcodeBatch initializer, camera setup, overlay, lifecycle) is preserved"
}
]
},
{
"id": 4,
"prompt": "I want to add MatrixScan Batch to my SwiftUI app. Here's my empty ContentView: ContentView.swift. I need to scan EAN-13 and Code128 barcodes. Please show me the complete updated code in your response.",
"expected_output": "The skill recognizes the SwiftUI path, creates a UIKit ScanViewController containing all BarcodeBatch APIs (context, mode, settings, camera, DataCaptureView, BarcodeBatchBasicOverlay, BarcodeBatchListener, lifecycle, deinit cleanup), and wraps it in a UIViewControllerRepresentable. The SwiftUI View struct contains no Scandit-specific code beyond using the representable. The setup checklist is shown.",
"files": [
"fixtures/ContentView.swift"
],
"assertions": [
{
"text": "A UIViewControllerRepresentable is defined that wraps a UIKit UIViewController"
},
{
"text": "All BarcodeBatch APIs (DataCaptureContext, BarcodeBatch, BarcodeBatchSettings, DataCaptureView, BarcodeBatchListener, camera lifecycle) live inside the UIKit view controller, NOT inside the SwiftUI View struct"
},
{
"text": "The SwiftUI View struct contains no Scandit-specific code beyond referencing the representable"
},
{
"text": "import ScanditBarcodeCapture is added"
},
{
"text": "DataCaptureContext.initialize(licenseKey: is called and DataCaptureContext.shared is used"
},
{
"text": "Symbology .ean13UPCA is enabled via settings.set(symbology:enabled:) (semantic)"
},
{
"text": "Symbology .code128 is enabled (semantic)"
},
{
"text": "Camera.default is used and BarcodeBatch.recommendedCameraSettings is applied via camera?.apply(_:completionHandler:) (semantic)"
},
{
"text": "context.setFrameSource(camera, completionHandler:) is called (semantic)"
},
{
"text": "BarcodeBatch(context: settings:) convenience initializer is used"
},
{
"text": "DataCaptureView is created and added with view.addSubview inside the view controller"
},
{
"text": "BarcodeBatchBasicOverlay(barcodeBatch:view:) is called (semantic)"
},
{
"text": "BarcodeBatchListener is implemented with barcodeBatch(_:didUpdate:frameData:)"
},
{
"text": "The didUpdate callback dispatches UI work via DispatchQueue.main.async"
},
{
"text": "viewWillAppear turns the camera on and sets barcodeBatch.isEnabled = true"
},
{
"text": "viewWillDisappear turns the camera off and sets barcodeBatch.isEnabled = false"
},
{
"text": "deinit calls barcodeBatch.removeListener(self)"
},
{
"text": "Setup checklist is shown and mentions adding ScanditBarcodeCapture and ScanditCaptureCore via Swift Package Manager"
},
{
"text": "Setup checklist mentions adding NSCameraUsageDescription to Info.plist"
},
{
"text": "A license key placeholder string is present"
}
]
},
{
"id": 5,
"prompt": "I want to show a small label above each tracked barcode in my MatrixScan Batch view controller — the label should display the barcode's data. Here is my view controller: IntegratedViewController.swift",
"expected_output": "The skill adds a BarcodeBatchAdvancedOverlay to the view controller, conforms to BarcodeBatchAdvancedOverlayDelegate, implements barcodeBatchAdvancedOverlay(_:viewFor:) to return a UILabel (or other UIView) showing trackedBarcode.barcode.data, and mentions that BarcodeBatchAdvancedOverlay requires the MatrixScan AR add-on license. Existing integration code (basic overlay, listener, lifecycle) is preserved.",
"files": [
"fixtures/IntegratedViewController.swift"
],
"assertions": [
{
"text": "BarcodeBatchAdvancedOverlay(barcodeBatch:view:) is created and stored on the view controller (semantic)"
},
{
"text": "advancedOverlay.delegate = self (or equivalent) is assigned"
},
{
"text": "BarcodeBatchAdvancedOverlayDelegate is adopted (e.g. via an extension)"
},
{
"text": "barcodeBatchAdvancedOverlay(_:viewFor:) is implemented and returns a UIView (e.g. UILabel) that displays trackedBarcode.barcode.data (semantic)"
},
{
"text": "Response mentions that BarcodeBatchAdvancedOverlay requires the MatrixScan AR add-on license"
},
{
"text": "Existing integration code (BarcodeBatch initializer, BarcodeBatchBasicOverlay, BarcodeBatchListener, camera lifecycle) is preserved"
}
],
"tags": [
"advanced-overlay-view"
]
},
{
"id": 6,
"prompt": "In my MatrixScan Batch view controller the highlights are drawn as rectangular frames. I'd prefer the dot style instead. Here is my view controller: IntegratedViewController.swift",
"expected_output": "The skill changes the BarcodeBatchBasicOverlay construction to pass the .dot style via the style: parameter of the convenience initializer, keeping the rest of the integration intact. It may mention that the style is read-only after construction (set it at init time) and that dotRadius tunes dot size.",
"files": [
"fixtures/IntegratedViewController.swift"
],
"assertions": [
{
"text": "BarcodeBatchBasicOverlay(barcodeBatch:view:style:) is used (the style-parameter convenience initializer)"
},
{
"text": "The style passed is .dot"
},
{
"text": "The style is set at construction time, not assigned to overlay.style afterward (style is read-only)"
},
{
"text": "Existing integration code (BarcodeBatch initializer, DataCaptureView, BarcodeBatchListener, camera lifecycle) is preserved"
}
],
"tags": [
"basic-overlay-style"
]
},
{
"id": 7,
"prompt": "When the user taps a highlighted barcode in my MatrixScan Batch view, I want to print the tapped barcode's data. Here is my view controller: IntegratedViewController.swift",
"expected_output": "The skill conforms to BarcodeBatchBasicOverlayDelegate, implements barcodeBatchBasicOverlay(_:didTap:) to read trackedBarcode.barcode.data, assigns overlay.delegate = self, and notes that the tap callback fires on the main thread and that the delegate requires the MatrixScan AR add-on license. Because brushFor is a required protocol member, the skill also implements it returning a real brush (e.g. BarcodeBatchBasicOverlay.defaultBrush(forStyle: overlay.style)) — NOT nil — so highlights stay visible and remain tappable.",
"files": [
"fixtures/IntegratedViewController.swift"
],
"assertions": [
{
"text": "BarcodeBatchBasicOverlayDelegate is adopted (e.g. via an extension)"
},
{
"text": "barcodeBatchBasicOverlay(_:didTap:) is implemented (semantic)"
},
{
"text": "trackedBarcode.barcode.data is read inside the didTap callback"
},
{
"text": "(semantic) the required barcodeBatchBasicOverlay(_:brushFor:) member returns a real brush (e.g. BarcodeBatchBasicOverlay.defaultBrush(forStyle:) or a custom Brush), NOT nil, so highlights remain visible and tappable"
},
{
"text": "overlay.delegate = self (or equivalent) is assigned"
},
{
"text": "Response mentions that using the BarcodeBatchBasicOverlayDelegate requires the MatrixScan AR add-on license"
},
{
"text": "Existing integration code (BarcodeBatch initializer, DataCaptureView, BarcodeBatchListener, camera lifecycle) is preserved"
}
],
"tags": [
"basic-overlay-tap"
]
},
{
"id": 8,
"prompt": "I already show a label above each tracked barcode using a BarcodeBatchAdvancedOverlay, but the labels overlap the codes. I want each label anchored to the top-center of its barcode and offset upward by one label height. Here is my view controller: IntegratedViewController.swift",
"expected_output": "The skill implements barcodeBatchAdvancedOverlay(_:anchorFor:) returning .topCenter and barcodeBatchAdvancedOverlay(_:offsetFor:) returning a PointWithUnit built from FloatWithUnit values in fraction units, keeping the existing viewFor implementation. It notes these delegate methods run on the main thread.",
"files": [
"fixtures/IntegratedViewController.swift"
],
"assertions": [
{
"text": "barcodeBatchAdvancedOverlay(_:anchorFor:) is implemented and returns .topCenter (semantic)"
},
{
"text": "barcodeBatchAdvancedOverlay(_:offsetFor:) is implemented and returns a PointWithUnit (semantic)"
},
{
"text": "The offset PointWithUnit is built with PointWithUnit(x:y:) from FloatWithUnit values (semantic)"
},
{
"text": "FloatWithUnit(value:unit:) is used with unit .fraction for the offset components (semantic)"
},
{
"text": "The vertical offset moves the view upward (a negative y FloatWithUnit value)"
},
{
"text": "Existing integration code (BarcodeBatchAdvancedOverlay creation, viewFor delegate method, BarcodeBatchListener, camera lifecycle) is preserved"
}
],
"tags": [
"advanced-overlay-positioning"
]
},
{
"id": 9,
"prompt": "In my MatrixScan Batch view controller I keep a running set of scanned barcode data. When a barcode leaves the camera view I want to remove it from that set. Here is my view controller: IntegratedViewController.swift",
"expected_output": "The skill updates barcodeBatch(_:didUpdate:frameData:) to read session.removedTrackedBarcodes (an array of Int tracking identifiers), copies it out before dispatching, and removes the matching entries on the main thread. It explains removedTrackedBarcodes holds the tracking identifiers of barcodes that left the frame, not TrackedBarcode objects.",
"files": [
"fixtures/IntegratedViewController.swift"
],
"assertions": [
{
"text": "session.removedTrackedBarcodes is accessed inside barcodeBatch(_:didUpdate:frameData:)"
},
{
"text": "The response treats session.removedTrackedBarcodes as an array of Int tracking identifiers (not TrackedBarcode objects)"
},
{
"text": "The removed identifiers are copied out of the session before DispatchQueue.main.async (session.removedTrackedBarcodes is not accessed inside the async block)"
},
{
"text": "UI/state updates that react to removal are dispatched via DispatchQueue.main.async"
},
{
"text": "Existing integration code (BarcodeBatch initializer, camera setup, overlay, lifecycle) is preserved"
}
],
"tags": [
"removed-barcodes"
]
},
{
"id": 10,
"prompt": "I want my MatrixScan Batch app to play a beep and vibrate each time a new barcode starts being tracked. Here is my view controller: IntegratedViewController.swift",
"expected_output": "The skill explains that BarcodeBatch has no built-in feedback (unlike BarcodeCapture/SparkScan), so feedback must be emitted manually. It calls Feedback.default.emit() from inside barcodeBatch(_:didUpdate:frameData:) when session.addedTrackedBarcodes is non-empty, dispatched to the main thread.",
"files": [
"fixtures/IntegratedViewController.swift"
],
"assertions": [
{
"text": "Response explains that BarcodeBatch does NOT emit feedback automatically and it must be triggered manually (semantic)"
},
{
"text": "Feedback.default is used to obtain the default feedback"
},
{
"text": "Feedback.default.emit() is called (the emit() method triggers sound and vibration)"
},
{
"text": "Feedback is emitted in response to session.addedTrackedBarcodes being non-empty (new barcodes this frame)"
},
{
"text": "The emit() call is dispatched on the main thread via DispatchQueue.main.async"
},
{
"text": "Existing integration code (BarcodeBatch initializer, camera setup, overlay, lifecycle) is preserved"
}
],
"tags": [
"feedback"
]
}
]
}
{
"skill_name": "matrixscan-batch-ios",
"evals": [
{
"id": 1,
"prompt": "Replace my AVFoundation AVCaptureMetadataOutput multi-barcode scanner with Scandit MatrixScan Batch. Every visible barcode (EAN-13, Code 128, QR) should be tracked at once and accumulated into a dedup list. Here is my view controller: AVFoundationViewController.swift",
"expected_output": "The skill removes the AVFoundation stack (AVCaptureSession / AVCaptureDeviceInput / AVCaptureMetadataOutput / AVCaptureVideoPreviewLayer setup, the AVCaptureMetadataOutputObjectsDelegate metadataOutput callback, the AVMetadataObject.ObjectType usage) and replaces it with MatrixScan Batch: DataCaptureContext.initialize + .shared, manual camera setup via Camera.default + BarcodeBatch.recommendedCameraSettings + context.setFrameSource, BarcodeBatchSettings with .ean13UPCA + .code128 + .qr, BarcodeBatch(context:settings:), DataCaptureView added with addSubview, BarcodeBatchBasicOverlay(barcodeBatch:view:), a BarcodeBatchListener whose barcodeBatch(_:didUpdate:frameData:) reads session.addedTrackedBarcodes (copied out, then dispatched to DispatchQueue.main.async). The ScannedBarcode model and the dedup logic are preserved. The setup checklist is shown.",
"files": [
"fixtures/AVFoundationViewController.swift"
],
"assertions": [
{
"text": "import AVFoundation is NOT present in the output"
},
{
"text": "import ScanditBarcodeCapture IS present in the output"
},
{
"text": "The AVCaptureSession / AVCaptureDeviceInput / AVCaptureDevice setup is removed (semantic)"
},
{
"text": "The AVCaptureMetadataOutput instance and its setMetadataObjectsDelegate wiring are removed (semantic)"
},
{
"text": "The AVCaptureMetadataOutputObjectsDelegate conformance and its metadataOutput(_:didOutput:from:) callback are removed (semantic)"
},
{
"text": "The AVCaptureVideoPreviewLayer is removed and replaced by a DataCaptureView (semantic)"
},
{
"text": "DataCaptureContext.initialize(licenseKey: is used and DataCaptureContext.shared is read"
},
{
"text": "BarcodeBatchSettings is constructed"
},
{
"text": "Symbology .ean13UPCA is enabled (mapped from AVMetadataObject.ObjectType.ean13) (semantic)"
},
{
"text": "Symbology .code128 is enabled (mapped from .code128) (semantic)"
},
{
"text": "Symbology .qr is enabled (mapped from .qr) (semantic)"
},
{
"text": "Camera.default is used to obtain the camera"
},
{
"text": "BarcodeBatch.recommendedCameraSettings is applied via camera?.apply(_:completionHandler:) (semantic)"
},
{
"text": "context.setFrameSource(camera, completionHandler:) is called (semantic)"
},
{
"text": "BarcodeBatch(context: settings:) convenience initializer is used"
},
{
"text": "DataCaptureView(context:frame:) is created and added with view.addSubview (semantic)"
},
{
"text": "BarcodeBatchBasicOverlay(barcodeBatch:view:) is called (semantic)"
},
{
"text": "The view controller conforms to BarcodeBatchListener and implements barcodeBatch(_:didUpdate:frameData:)"
},
{
"text": "barcodeBatch(_:didUpdate:frameData:) reads session.addedTrackedBarcodes (the new-this-frame collection)"
},
{
"text": "Session data is copied out before DispatchQueue.main.async and UI/state updates run inside DispatchQueue.main.async"
},
{
"text": "The existing ScannedBarcode struct is preserved in the output"
},
{
"text": "The deduplication logic (the seenValues set / checking before appending) is preserved in the new flow"
},
{
"text": "Setup checklist is shown and mentions adding ScanditBarcodeCapture and ScanditCaptureCore via Swift Package Manager"
},
{
"text": "Setup checklist mentions adding NSCameraUsageDescription to Info.plist"
},
{
"text": "A summary of changes is shown with Removed and Added sections"
}
],
"tags": [
"third-party-migration"
]
}
]
}
{
"skill_name": "matrixscan-batch-ios",
"evals": [
{
"id": 1,
"prompt": "I need to upgrade my iOS MatrixScan app from Scandit SDK v6 to v7. Here is my current view controller: ViewControllerV6.swift.",
"expected_output": "The skill renames every v6 BarcodeTracking* type to its v7 BarcodeBatch* equivalent (mode, settings, listener, session, basic overlay) and renames the listener delegate method barcodeTracking(_:didUpdate:frameData:) to barcodeBatch(_:didUpdate:frameData:). It modernizes the context to DataCaptureContext.initialize(licenseKey:) + DataCaptureContext.shared and the camera setup to BarcodeBatch.recommendedCameraSettings. The convenience-initializer shape BarcodeBatch(context:settings:) is preserved. Provides the v6→v7 migration guide URL.",
"files": [
"fixtures/ViewControllerV6.swift"
],
"assertions": [
{
"text": "The BarcodeTracking mode type is renamed to BarcodeBatch in the rewritten code blocks"
},
{
"text": "BarcodeTrackingSettings is renamed to BarcodeBatchSettings in the rewritten code blocks"
},
{
"text": "BarcodeTrackingSession is renamed to BarcodeBatchSession in the rewritten code blocks"
},
{
"text": "BarcodeTrackingListener is renamed to BarcodeBatchListener in the rewritten code blocks"
},
{
"text": "BarcodeTrackingBasicOverlay is renamed to BarcodeBatchBasicOverlay in the rewritten code blocks"
},
{
"text": "The listener delegate method barcodeBatch(_:didUpdate:frameData:) is present in the rewritten code blocks"
},
{
"text": "The old delegate method barcodeTracking(_:didUpdate:frameData:) is NOT present in the rewritten code blocks"
},
{
"text": "No BarcodeTracking-named identifier remains in the rewritten code blocks"
},
{
"text": "DataCaptureContext.initialize(licenseKey: is present in the rewritten code blocks"
},
{
"text": "DataCaptureContext.shared is read after initialize"
},
{
"text": "The deprecated DataCaptureContext(licenseKey:) constructor is NOT used in the rewritten code blocks (semantic)"
},
{
"text": "BarcodeBatch.recommendedCameraSettings is used for camera setup"
},
{
"text": "The v6 hand-rolled CameraSettings() with preferredResolution = .auto is NOT present in the rewritten code blocks"
},
{
"text": "The BarcodeBatch(context:settings:) convenience initializer shape is preserved (not changed to a factory like .forDataCaptureContext) (semantic)"
},
{
"text": "The iOS lifecycle (viewWillAppear / viewWillDisappear) and deinit removeListener are preserved through the rename"
},
{
"text": "The v6→v7 migration guide URL https://docs.scandit.com/sdks/ios/migrate-6-to-7/ is provided"
}
],
"tags": [
"version-migration"
]
},
{
"id": 2,
"prompt": "I'm upgrading my Scandit iOS MatrixScan Batch SDK from v7 to v8. What do I need to change in my code?",
"expected_output": "The skill explains that native iOS BarcodeBatch has no breaking API changes in v7→v8 — the BarcodeBatch(context:settings:) initializer, BarcodeBatchListener, BarcodeBatchSession, and the overlays are unchanged. The only thing to watch for is the VideoResolution.auto deprecation in custom camera settings. It does NOT tell the user to swap BarcodeBatch(context:settings:) for a different constructor + addMode pattern (that change is for cross-platform SDKs only). Provides the v7→v8 migration guide URL.",
"files": [],
"assertions": [
{
"text": "The skill states there are no breaking API changes for native iOS BarcodeBatch in v7→v8"
},
{
"text": "VideoResolution.auto (or .auto resolution) deprecation is mentioned"
},
{
"text": "The skill does NOT instruct the user to replace BarcodeBatch(context:settings:) with a different constructor + addMode pattern (that is a cross-platform SDK change) (semantic)"
},
{
"text": "The skill does NOT instruct the user to rename BarcodeBatch(context:settings:) to a factory method (semantic)"
},
{
"text": "The v7→v8 migration guide URL https://docs.scandit.com/sdks/ios/migrate-7-to-8/ is provided"
}
],
"tags": [
"version-migration"
]
},
{
"id": 3,
"prompt": "I want to upgrade my MatrixScan Batch iOS integration to the latest Scandit SDK version. Here's my code and Package.resolved.",
"expected_output": "The skill reads Package.resolved to detect v6.28.1, determines the target is v8, and applies both migrations in order (6→7 then 7→8) without asking the user for their version. The 6→7 BarcodeTracking→BarcodeBatch rename and context/camera modernization are applied.",
"files": [
"fixtures/ViewControllerV6.swift",
"fixtures/Package_v6.resolved"
],
"assertions": [
{
"text": "Summary mentions the detected version (6.28.1 or v6)"
},
{
"text": "Did not ask the user for their version — proceeded autonomously"
},
{
"text": "No BarcodeTracking-named identifier remains in the rewritten code blocks (6→7 rename applied)"
},
{
"text": "BarcodeBatch is used as the mode type in the rewritten code blocks"
},
{
"text": "DataCaptureContext.initialize(licenseKey: is present in the rewritten code blocks"
},
{
"text": "The deprecated DataCaptureContext(licenseKey:) constructor is NOT used in the rewritten code blocks (semantic)"
},
{
"text": "BarcodeBatch.recommendedCameraSettings is used (camera setup updated)"
},
{
"text": "The v6 hand-rolled CameraSettings() with preferredResolution = .auto is NOT present in the rewritten code blocks"
}
],
"tags": [
"version-migration"
]
}
]
}
BarcodeBatch (MatrixScan Batch) iOS Integration Guide
BarcodeBatch is the multi-barcode tracking mode. It simultaneously tracks all barcodes visible in the camera feed, reporting additions, position updates, and removals on every frame. Unlike BarcodeCapture (which scans one barcode at a time), BarcodeBatch continuously tracks every barcode in view — it does not stop or disable after a detection. The camera and lifecycle are managed manually, exactly like BarcodeCapture.
Examples below use Swift and a UIKit UIViewController. SwiftUI is covered at the end — the canonical approach is to wrap the UIKit view controller in a UIViewControllerRepresentable.
Prerequisites
- Scandit Data Capture SDK for iOS — add via Swift Package Manager:
- URL:
https://github.com/Scandit/datacapture-spm - Add
ScanditBarcodeCaptureandScanditCaptureCorepackage products to your target - A valid Scandit license key:
- Sign in at https://ssl.scandit.com to generate one
- No account yet? Sign up at https://ssl.scandit.com/dashboard/sign-up?p=test
NSCameraUsageDescriptioninInfo.plist
Integration flow
Ask the user which barcode symbologies they need to scan. When asking, mention that enabling only the symbologies actually needed improves tracking performance and accuracy.
Once the user responds, ask them which view controller (or SwiftUI view) they'd like to integrate BarcodeBatch into. Then write the integration code directly into that file. Do not just show the code in chat; apply it to the file.
After providing the code, show this setup checklist:
Setup checklist: 1. Add ScanditBarcodeCapture and ScanditCaptureCore via Swift Package Manager: https://github.com/Scandit/datacapture-spm 2. Make sure you have NSCameraUsageDescription added to your Info.plist 3. Replace -- ENTER YOUR SCANDIT LICENSE KEY HERE -- with your key from https://ssl.scandit.com
Framework import
import ScanditBarcodeCapture
import UIKitBarcodeBatch, BarcodeBatchSettings, BarcodeBatchListener, BarcodeBatchSession, BarcodeBatchBasicOverlay, BarcodeBatchAdvancedOverlay, TrackedBarcode, DataCaptureContext, DataCaptureView, and Camera all live in ScanditBarcodeCapture.
Step 1 — Create the DataCaptureContext
The canonical iOS pattern uses the shared singleton — initialize once with the license key, then read DataCaptureContext.shared:
private lazy var context: DataCaptureContext = {
DataCaptureContext.initialize(licenseKey: "-- ENTER YOUR SCANDIT LICENSE KEY HERE --")
return DataCaptureContext.shared
}()Step 2 — Configure BarcodeBatchSettings
All symbologies are disabled by default. Enable each one explicitly; enabling only what is needed reduces tracking overhead.
let settings = BarcodeBatchSettings()
settings.set(symbology: .ean13UPCA, enabled: true)
settings.set(symbology: .ean8, enabled: true)
settings.set(symbology: .code128, enabled: true)BarcodeBatchSettings Members
| Member | Description |
|---|---|
set(symbology:enabled:) | Enable or disable one symbology. |
enableSymbologies(_:) | Enable a Set<Symbology> in one call. |
settings(for:) | Get per-symbology SymbologySettings (e.g. activeSymbolCounts, isColorInvertedEnabled, checksums). |
Symbology fine-tuning
settings.settings(for: .code39).activeSymbolCounts = Set(7...20)
settings.settings(for: .code128).isColorInvertedEnabled = trueStep 3 — Camera setup
The camera is set up manually. Apply BarcodeBatch.recommendedCameraSettings (a static type property) to the default camera, then assign the camera as the frame source.
private var camera: Camera?
// In setupRecognition():
camera = Camera.default
context.setFrameSource(camera, completionHandler: nil)
let cameraSettings = BarcodeBatch.recommendedCameraSettings
camera?.apply(cameraSettings, completionHandler: nil)If the user needs to tune resolution, zoom, focus, torch, macro, or adaptive exposure, modify the recommended settings before applying — do not construct CameraSettings() from scratch:
let cameraSettings = BarcodeBatch.recommendedCameraSettings
cameraSettings.preferredResolution = .uhd4k
camera?.apply(cameraSettings, completionHandler: nil)Step 4 — Create BarcodeBatch
BarcodeBatch uses a direct convenience initializer (not a factory method). Passing a non-nil context attaches the mode to the context automatically.
private var barcodeBatch: BarcodeBatch!
// In setupRecognition():
barcodeBatch = BarcodeBatch(context: context, settings: settings)
barcodeBatch.addListener(self)BarcodeBatch Members
| Member | Description |
|---|---|
BarcodeBatch(context:settings:) | Convenience initializer — when context is non-nil, the mode is attached to the context. |
isEnabled: Bool | Pause/resume tracking without tearing down the camera. |
addListener(_:) / removeListener(_:) | Register or remove a BarcodeBatchListener (weak reference). |
apply(_:completionHandler:) | Update settings at runtime. |
BarcodeBatch.recommendedCameraSettings | Static — returns recommended CameraSettings. |
Step 5 — DataCaptureView
DataCaptureView is a UIView. Create it with the context and add it as a subview manually — the view does not auto-attach.
private var captureView: DataCaptureView!
// In setupRecognition():
captureView = DataCaptureView(context: context, frame: view.bounds)
captureView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(captureView)Step 6 — BarcodeBatchBasicOverlay
BarcodeBatchBasicOverlay renders a highlight frame or dot over each tracked barcode. The convenience initializer auto-adds the overlay to the data capture view.
// Default style (.frame):
let overlay = BarcodeBatchBasicOverlay(barcodeBatch: barcodeBatch, view: captureView)
// Or choose a style explicitly:
let overlay = BarcodeBatchBasicOverlay(barcodeBatch: barcodeBatch, view: captureView, style: .dot)BarcodeBatchBasicOverlay Members
| Member | Description |
|---|---|
init(barcodeBatch:view:) | Convenience init — auto-adds the overlay to the view. Default .frame style. |
init(barcodeBatch:view:style:) | Convenience init — same, with explicit style. |
delegate | BarcodeBatchBasicOverlayDelegate? — for per-barcode brush customization. |
brush | Brush? — uniform brush for all tracked barcodes when no delegate is set. |
style | BarcodeBatchBasicOverlayStyle — .frame or .dot (read-only). |
dotRadius | FloatWithUnit — controls dot size when style is .dot (default 0.03 fraction). |
setBrush(_:for:) | Imperatively set the brush for a specific barcode. |
clearTrackedBarcodeBrushes() | Clear all custom brushes. |
shouldShowScanAreaGuides | Debug: show the active scan area outline. |
Reacting to taps on a barcode (requires MatrixScan AR add-on)
Conform to BarcodeBatchBasicOverlayDelegate and implement didTap to react to the user tapping a highlight. The didTap callback fires on the main thread.
brushFor is a required member of the protocol, so adopting the delegate forces you to implement it too. It must return a brush — returning nil draws nothing for that barcode, which removes the highlight, and with no highlight there is nothing for the user to tap. For a tap-only feature, return the default brush so highlights stay visible:
extension ScanViewController: BarcodeBatchBasicOverlayDelegate {
// Required by the protocol. Return a real brush so highlights stay
// visible and tappable — returning nil blanks the highlight and the
// tap can never fire.
func barcodeBatchBasicOverlay(
_ overlay: BarcodeBatchBasicOverlay,
brushFor trackedBarcode: TrackedBarcode
) -> Brush? {
return BarcodeBatchBasicOverlay.defaultBrush(forStyle: overlay.style)
}
func barcodeBatchBasicOverlay(
_ overlay: BarcodeBatchBasicOverlay,
didTap trackedBarcode: TrackedBarcode
) {
// React to the user tapping a barcode highlight.
}
}Assign the delegate after creating the overlay:
overlay.delegate = selfMatrixScan AR add-on required — adoptingBarcodeBatchBasicOverlayDelegate(the only way to receivedidTap), thebrushForcallback, andsetBrush(_:for:)all require the add-on. A uniform default brush (no delegate) does not.
Per-barcode brush customization (requires MatrixScan AR add-on)
The same brushFor callback returns a different brush per barcode. It fires on the rendering thread. Returning nil draws nothing for that barcode — only do this when you genuinely want that code invisible (and not tappable):
func barcodeBatchBasicOverlay(
_ overlay: BarcodeBatchBasicOverlay,
brushFor trackedBarcode: TrackedBarcode
) -> Brush? {
switch trackedBarcode.barcode.symbology {
case .ean13UPCA:
return Brush(fill: UIColor.green.withAlphaComponent(0.4), stroke: .green, strokeWidth: 2)
default:
// No highlight for other symbologies — they are also not tappable.
return nil
}
}Step 7 — BarcodeBatchListener
Conform to BarcodeBatchListener to receive per-frame session updates. barcodeBatch(_:didUpdate:frameData:) is called on a background queue — do not touch UIKit from inside it without dispatching to the main queue, and do not hold session collection references outside the callback.
extension ScanViewController: BarcodeBatchListener {
func barcodeBatch(
_ barcodeBatch: BarcodeBatch,
didUpdate session: BarcodeBatchSession,
frameData: FrameData
) {
// Called on a background queue — copy data, then dispatch UI work.
let addedData = session.addedTrackedBarcodes.compactMap { $0.barcode.data }
let removedIdentifiers = session.removedTrackedBarcodes
DispatchQueue.main.async {
for data in addedData {
// handle newly tracked barcode data
_ = data
}
for identifier in removedIdentifiers {
// handle barcode that left the frame
_ = identifier
}
}
}
}Register the listener after constructing BarcodeBatch:
barcodeBatch.addListener(self)BarcodeBatchListener Protocol
| Method | Description |
|---|---|
barcodeBatch(_:didUpdate:frameData:) | Required. Called every processed frame on a background queue — copy data and dispatch UI work. |
didStartObserving(_:) | Optional. Listener was registered. |
didStopObserving(_:) | Optional. Listener was removed. |
BarcodeBatchSession Properties
| Property | Type | Description |
|---|---|---|
trackedBarcodes | Dictionary<Int, TrackedBarcode> | All currently tracked barcodes, keyed by tracking ID. |
addedTrackedBarcodes | Array<TrackedBarcode> | Barcodes newly tracked in this frame. |
updatedTrackedBarcodes | Array<TrackedBarcode> | Barcodes whose position changed in this frame. |
removedTrackedBarcodes | Array<Int> | Tracking IDs of barcodes that left the view. |
frameSequenceId | Int | Identifier of the current frame sequence. |
reset() | — | Clear all tracked state (call only from within the callback). |
Important: Do not hold references totrackedBarcodes,addedTrackedBarcodes,updatedTrackedBarcodes, orremovedTrackedBarcodesoutsidebarcodeBatch(_:didUpdate:frameData:). Copy the data you need before the callback returns. IndividualTrackedBarcodeinstances can be safely retained.
TrackedBarcode Properties
| Property | Description |
|---|---|
barcode | The decoded Barcode. Access .data, .symbology, etc. |
identifier | Int — unique tracking ID. Reused after the barcode leaves the frame. |
location | Quadrilateral — barcode position in image-space coordinates. |
Reacting to barcodes leaving the frame
session.removedTrackedBarcodes is an Array<Int> — the tracking identifiers of barcodes that left the view in this frame, not TrackedBarcode objects. Use it to drop entries from a running collection keyed by tracking ID. Like every other session collection, copy it out before the callback returns:
func barcodeBatch(
_ barcodeBatch: BarcodeBatch,
didUpdate session: BarcodeBatchSession,
frameData: FrameData
) {
let removedIdentifiers = session.removedTrackedBarcodes // [Int]
DispatchQueue.main.async {
for identifier in removedIdentifiers {
// remove the entry tracked under this identifier
_ = identifier
}
}
}addedTrackedBarcodes and updatedTrackedBarcodes return Array<TrackedBarcode>; only removedTrackedBarcodes returns identifiers, because the barcodes it refers to are no longer tracked.
Feedback (sound / vibration)
BarcodeBatch has no built-in feedback — unlike BarcodeCapture or SparkScan, it never plays a sound or vibrates on its own, because it continuously tracks many barcodes rather than committing to a single scan. To give the user audible/haptic feedback (e.g. when a new barcode starts being tracked), emit a Feedback manually from inside the listener callback.
func barcodeBatch(
_ barcodeBatch: BarcodeBatch,
didUpdate session: BarcodeBatchSession,
frameData: FrameData
) {
let hasNewBarcodes = !session.addedTrackedBarcodes.isEmpty
DispatchQueue.main.async {
if hasNewBarcodes {
Feedback.default.emit()
}
}
}Feedback.defaultis a static property returning the default feedback (default beep + default vibration).Feedback.default.emit()triggers that sound and vibration. It is influenced by the device's ring/volume settings.- For a custom feedback, construct one with
Feedback(vibration:sound:)— e.g.Feedback(vibration: .default, sound: .default)— store it on the view controller, and call.emit()on that instance.Vibration.default,Sound.default, and the haptic variants (Vibration.successHapticFeedback,Vibration.selectionHapticFeedback, etc.) are all available. - Emit on the main thread (dispatch from the background listener callback), and decide when to emit from the session deltas — typically
addedTrackedBarcodes(newly tracked) rather than every frame, so you do not beep continuously.
Feedback, Vibration, and Sound all live in ScanditCaptureCore (re-exported through ScanditBarcodeCapture).
Step 8 — Lifecycle management
Drive the camera and isEnabled flag from viewWillAppear and viewWillDisappear. Remove the listener in deinit to make the lifecycle explicit (listeners are weakly held, so missing this won't leak, but it is best practice).
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
barcodeBatch.isEnabled = true
camera?.switch(toDesiredState: .on)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
barcodeBatch.isEnabled = false
camera?.switch(toDesiredState: .off)
}
deinit {
barcodeBatch.removeListener(self)
}When using the shared singleton (DataCaptureContext.shared), modes stay attached for the app's lifetime — removing the listener is the only cleanup needed.DataCaptureContextdoes exposeremoveCurrentMode(),removeMode(_:), anddispose()if explicit teardown is required; the singleton flow simply doesn't need them.
Complete minimal example (UIKit)
import ScanditBarcodeCapture
import UIKit
class ScanViewController: UIViewController {
private lazy var context: DataCaptureContext = {
DataCaptureContext.initialize(licenseKey: "-- ENTER YOUR SCANDIT LICENSE KEY HERE --")
return DataCaptureContext.shared
}()
private var camera: Camera?
private var barcodeBatch: BarcodeBatch!
private var captureView: DataCaptureView!
private var overlay: BarcodeBatchBasicOverlay!
override func viewDidLoad() {
super.viewDidLoad()
setupRecognition()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
barcodeBatch.isEnabled = true
camera?.switch(toDesiredState: .on)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
barcodeBatch.isEnabled = false
camera?.switch(toDesiredState: .off)
}
deinit {
barcodeBatch.removeListener(self)
}
private func setupRecognition() {
camera = Camera.default
context.setFrameSource(camera, completionHandler: nil)
let cameraSettings = BarcodeBatch.recommendedCameraSettings
camera?.apply(cameraSettings, completionHandler: nil)
let settings = BarcodeBatchSettings()
settings.set(symbology: .ean13UPCA, enabled: true)
settings.set(symbology: .code128, enabled: true)
barcodeBatch = BarcodeBatch(context: context, settings: settings)
barcodeBatch.addListener(self)
captureView = DataCaptureView(context: context, frame: view.bounds)
captureView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(captureView)
overlay = BarcodeBatchBasicOverlay(barcodeBatch: barcodeBatch, view: captureView)
}
}
extension ScanViewController: BarcodeBatchListener {
func barcodeBatch(
_ barcodeBatch: BarcodeBatch,
didUpdate session: BarcodeBatchSession,
frameData: FrameData
) {
let addedData = session.addedTrackedBarcodes.compactMap { $0.barcode.data }
DispatchQueue.main.async {
for data in addedData {
_ = data
}
}
}
}Optional: BarcodeBatchAdvancedOverlay (requires MatrixScan AR add-on)
BarcodeBatchAdvancedOverlay anchors a custom UIView to each tracked barcode in real time. The convenience initializer auto-adds the overlay to the data capture view.
private var advancedOverlay: BarcodeBatchAdvancedOverlay!
// In setupRecognition(), after creating captureView:
advancedOverlay = BarcodeBatchAdvancedOverlay(barcodeBatch: barcodeBatch, view: captureView)
advancedOverlay.delegate = selfextension ScanViewController: BarcodeBatchAdvancedOverlayDelegate {
func barcodeBatchAdvancedOverlay(
_ overlay: BarcodeBatchAdvancedOverlay,
viewFor trackedBarcode: TrackedBarcode
) -> UIView? {
let label = UILabel()
label.text = trackedBarcode.barcode.data
label.backgroundColor = .white
label.sizeToFit()
return label
}
func barcodeBatchAdvancedOverlay(
_ overlay: BarcodeBatchAdvancedOverlay,
anchorFor trackedBarcode: TrackedBarcode
) -> Anchor {
return .topCenter
}
func barcodeBatchAdvancedOverlay(
_ overlay: BarcodeBatchAdvancedOverlay,
offsetFor trackedBarcode: TrackedBarcode
) -> PointWithUnit {
return PointWithUnit(
x: FloatWithUnit(value: 0, unit: .fraction),
y: FloatWithUnit(value: -1, unit: .fraction)
)
}
}All three advanced-overlay delegate methods are called on the main thread.
To update the view for a specific barcode imperatively (e.g. after a data lookup):
advancedOverlay.setView(updatedView, for: trackedBarcode)
advancedOverlay.setAnchor(.topCenter, for: trackedBarcode)
advancedOverlay.setOffset(offset, for: trackedBarcode)
advancedOverlay.clearTrackedBarcodeViews() // remove all viewsBarcodeBatchAdvancedOverlay Members
| Member | Description |
|---|---|
init(barcodeBatch:view:) | Convenience init — auto-adds the overlay to the view. |
delegate | BarcodeBatchAdvancedOverlayDelegate? |
setView(_:for:) | Set or update the UIView for a barcode. Pass nil to remove. Thread-safe. |
setAnchor(_:for:) | Override the anchor for a barcode. Thread-safe. |
setOffset(_:for:) | Override the offset for a barcode. Thread-safe. |
clearTrackedBarcodeViews() | Remove all anchored views. |
shouldShowScanAreaGuides | Debug: show the active scan area. |
Imperatively set values (setView/setAnchor/setOffset) take precedence over delegate callbacks — if a value has been set imperatively, the delegate method is not called.
For additional listener methods or tap handling, fetch the Adding AR Overlays page.
SwiftUI
DataCaptureView is a UIView — it cannot be dropped into SwiftUI directly. Wrap the scanning view controller in a UIViewControllerRepresentable and keep every BarcodeBatch API call (context, mode, settings, view, lifecycle) inside the wrapped UIKit view controller. Mixing BarcodeBatch APIs into a SwiftUI View struct breaks the SDK's view lifecycle expectations.
The Scandit SwiftUI Get Started page also documents aUIViewRepresentable+Coordinatoralternative, where the coordinator owns the SDK objects directly. Prefer theUIViewControllerRepresentablepattern below — it keeps the UIKit lifecycle (viewWillAppear/viewWillDisappear/deinit) intact and the same view controller works when reused from UIKit. Only fall back to the coordinator pattern if the project already has a strong reason to.
Canonical shape:
import SwiftUI
import ScanditBarcodeCapture
struct ScanView: View {
var body: some View {
ScanViewControllerRepresentable()
.edgesIgnoringSafeArea(.all)
}
}
struct ScanViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ScanViewController {
ScanViewController()
}
func updateUIViewController(_ uiViewController: ScanViewController, context: Context) {}
}ScanViewController is the exact UIKit class from the minimal example above — no changes. The SwiftUI View struct contains no Scandit code.
SwiftUI cleanup
The view controller's viewWillAppear / viewWillDisappear / deinit still fire when SwiftUI presents and dismisses the representable, so the UIKit lifecycle code carries over unchanged — no extra SwiftUI-side teardown is required. When SwiftUI removes the representable from the view tree, it releases its strong reference to ScanViewController, which triggers deinit and the removeListener call.
If you need to react to SwiftUI-side teardown explicitly (e.g. to stop a related service), implement the static dismantleUIViewController on the representable:
struct ScanViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ScanViewController {
ScanViewController()
}
func updateUIViewController(_ uiViewController: ScanViewController, context: Context) {}
static func dismantleUIViewController(_ uiViewController: ScanViewController, coordinator: ()) {
// Optional: extra teardown beyond what the view controller's deinit handles.
}
}Do not move removeListener or camera-off calls out of the view controller into dismantleUIViewController — keep BarcodeBatch lifecycle inside the UIKit class so the same view controller works when used directly from UIKit.
Key Rules
1. Convenience initializer, not factory — BarcodeBatch(context: context, settings: settings) is a direct convenience initializer. Passing a non-nil context attaches the mode to the context. 2. Manual camera — Camera.default, then context.setFrameSource(camera, completionHandler: nil) and camera?.apply(BarcodeBatch.recommendedCameraSettings, completionHandler: nil). Drive on/off from viewWillAppear/viewWillDisappear. 3. Background queue — barcodeBatch(_:didUpdate:frameData:) runs off the main thread. Copy the data you need, then DispatchQueue.main.async {} for UI work. 4. Don't hold session references — trackedBarcodes, addedTrackedBarcodes, updatedTrackedBarcodes, removedTrackedBarcodes are only safe within the callback. Copy data before the callback returns. 5. Overlay auto-adds — BarcodeBatchBasicOverlay(barcodeBatch:view:) and BarcodeBatchAdvancedOverlay(barcodeBatch:view:) both add themselves to the DataCaptureView automatically. 6. DataCaptureView is manual — call view.addSubview(captureView) yourself; unlike BarcodeArView, DataCaptureView does not auto-attach. 7. AR add-on required — per-barcode brush customization (the brushFor delegate, setBrush(_:for:)) and BarcodeBatchAdvancedOverlay both require the MatrixScan AR add-on license. 8. isEnabled for pause/resume — toggle barcodeBatch.isEnabled to pause and resume tracking without tearing down the camera. 9. Cleanup — call barcodeBatch.removeListener(self) in deinit. Listeners are weakly held, so this is best practice, not a leak prevention. When using the shared singleton, modes stay attached for the app's lifetime — no extra teardown is required. removeCurrentMode() / dispose() exist on DataCaptureContext if you want explicit teardown. In SwiftUI, the wrapped view controller's deinit fires when the representable is removed. 10. Symbologies — all disabled by default; enable only what is needed. Cases are camelCase: .ean13UPCA, .code128, .qr. 11. Camera permission — add NSCameraUsageDescription to Info.plist. iOS shows the permission prompt automatically when the camera first starts.
MatrixScan Batch (BarcodeBatch) iOS Migration Guide
Step 1: Detect the installed SDK version
Before making any changes, find out which version of the Scandit SDK the project currently has installed.
Check in this order:
1. Swift Package Manager — open <ProjectRoot>/Package.resolved (or <ProjectRoot>/<App>.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved) and look for the entry with "identity": "datacapture-spm". The "version" field is the installed version. 2. CocoaPods — open Podfile.lock and look for ScanditBarcodeCapture or ScanditCaptureCore. The version number is on the same line.
Once you know the installed version, determine which migration path applies:
| Installed version | Target version | Action |
|---|---|---|
| 6.x | 7.x | Apply the 6 → 7 migration below |
| 7.x | 8.x | Apply the 7 → 8 migration below |
| 6.x | 8.x | Apply both migrations in order (6→7 first, then 7→8) |
If you cannot find Package.resolved or Podfile.lock, ask the user which version they are migrating from.
---
Step 2: Update the dependency version
Before touching source files, update the SDK version in the dependency manager:
- SPM: In Xcode → Package Dependencies, update
datacapture-spmto the target version. - CocoaPods: Update the version constraint in
Podfile, then runpod update.
Ask the user which dependency manager they use if it's not clear from the project.
---
Step 3: Apply source code changes
Find the files that use MatrixScan Batch (search for BarcodeTracking, BarcodeBatch, BarcodeTrackingSettings, BarcodeTrackingBasicOverlay, BarcodeTrackingListener) and apply the relevant changes below directly to those files.
---
Migration: 6 → 7
The 6→7 step for MatrixScan Batch has three things to handle: the BarcodeTracking → BarcodeBatch rename (the headline change), a context-construction update, and a camera setup update. Go through every section below and apply each change that matches the project.
BarcodeTracking → BarcodeBatch rename
In SDK 7 the entire MatrixScan tracking API was renamed from BarcodeTracking* to BarcodeBatch*. Rename every occurrence:
| v6 (BarcodeTracking) | v7+ (BarcodeBatch) |
|---|---|
BarcodeTracking | BarcodeBatch |
BarcodeTrackingSettings | BarcodeBatchSettings |
BarcodeTrackingListener | BarcodeBatchListener |
BarcodeTrackingSession | BarcodeBatchSession |
BarcodeTrackingBasicOverlay | BarcodeBatchBasicOverlay |
BarcodeTrackingBasicOverlayDelegate | BarcodeBatchBasicOverlayDelegate |
BarcodeTrackingAdvancedOverlay | BarcodeBatchAdvancedOverlay |
BarcodeTrackingAdvancedOverlayDelegate | BarcodeBatchAdvancedOverlayDelegate |
The delegate method names follow the rename too:
Before (v6):
func barcodeTracking(_ barcodeTracking: BarcodeTracking,
didUpdate session: BarcodeTrackingSession,
frameData: FrameData) { }After (v7+):
func barcodeBatch(_ barcodeBatch: BarcodeBatch,
didUpdate session: BarcodeBatchSession,
frameData: FrameData) { }Apply the same first-label rename to the overlay delegate callbacks: barcodeTrackingBasicOverlay(_:brushFor:) → barcodeBatchBasicOverlay(_:brushFor:), barcodeTrackingBasicOverlay(_:didTap:) → barcodeBatchBasicOverlay(_:didTap:), and the advanced-overlay callbacks (viewFor / anchorFor / offsetFor) likewise. The overlay convenience initializers also rename their first argument label: BarcodeTrackingBasicOverlay(barcodeTracking:view:) → BarcodeBatchBasicOverlay(barcodeBatch:view:).
The import stays import ScanditBarcodeCapture — only the symbol names change, not the framework.
When done, verify no BarcodeTracking-named identifier remains.
Context construction — update to the v7+ shared singleton pattern
In v7+ the recommended way to construct the context is DataCaptureContext.initialize(licenseKey:) followed by reading DataCaptureContext.shared. The bare DataCaptureContext(licenseKey:) constructor is deprecated.
Before (v6 — replace this):
let context = DataCaptureContext(licenseKey: "-- KEY --")After (v7+):
DataCaptureContext.initialize(licenseKey: "-- KEY --")
let context = DataCaptureContext.sharedIf DataCaptureContext.shared is already in use, skip this section.
Camera setup — update to the v7+ recommended pattern
Look for an explicit CameraSettings() construction, preferredResolution = .auto (or VideoResolution.auto), or a camera?.apply(cameraSettings) whose settings were not obtained from the recommended settings. Replace the block with BarcodeBatch.recommendedCameraSettings — the canonical API from v7 onwards. Inform the user that VideoResolution.auto will be formally deprecated in v8.
Before (v6 pattern — remove this):
let cameraSettings = CameraSettings()
cameraSettings.preferredResolution = .auto
let camera = Camera.default
camera?.apply(cameraSettings)After (v7+ pattern — use this):
let camera = Camera.default
camera?.apply(BarcodeBatch.recommendedCameraSettings, completionHandler: nil)If BarcodeBatch.recommendedCameraSettings is already in use, skip this section.
Initializer shape is unchanged
BarcodeBatch(context:settings:) is the convenience initializer in v7+ (the renamed equivalent of the v6 BarcodeTracking(context:settings:)). Do not swap it for a factory like .forDataCaptureContext(...) — that is the Android/cross-platform shape, not native Swift.
---
Migration: 7 → 8
The 7→8 step for native iOS BarcodeBatch has no breaking API changes. The factory-method deprecations listed in the official migration guide apply to cross-platform SDKs (React Native, Flutter, Capacitor) — not to native Swift, where BarcodeBatch(context:settings:) remains the correct API.
VideoResolution.auto deprecated
If the project creates a CameraSettings with VideoResolution.auto, replace it with the recommended camera settings:
v7 (deprecated):
let cameraSettings = CameraSettings()
cameraSettings.preferredResolution = .auto
camera?.apply(cameraSettings, completionHandler: nil)v8:
camera?.apply(BarcodeBatch.recommendedCameraSettings, completionHandler: nil)If the project already uses BarcodeBatch.recommendedCameraSettings, no action is needed.
No other breaking BarcodeBatch changes
The BarcodeBatch(context:settings:) initializer, BarcodeBatchListener, BarcodeBatchSession, BarcodeBatchBasicOverlay(barcodeBatch:view:), and BarcodeBatchAdvancedOverlay(barcodeBatch:view:) are all unchanged in v8 for native iOS.
---
After applying changes
1. Build the project. Fix any remaining compile errors using the API reference (linked in SKILL.md). 2. Let the user know they can check the full list of SDK changes in the official migration guides:
- 6 → 7: https://docs.scandit.com/sdks/ios/migrate-6-to-7/
- 7 → 8: https://docs.scandit.com/sdks/ios/migrate-7-to-8/
3. Show the user a summary of only the changes actually made: which files were edited, which types were renamed, and anything that required a judgment call. Do not list APIs that were already correct or unchanged. 4. If compile errors persist after the changes above, fetch the BarcodeBatch API reference (https://docs.scandit.com/data-capture-sdk/ios/barcode-capture/api.html) to find the correct API before guessing.
Third-Party Multi-Barcode Scanner → MatrixScan Batch Migration (iOS)
This guide covers replacing a custom multi-barcode scanner — most commonly AVFoundation (AVCaptureMetadataOutput) or Apple's VisionKit DataScannerViewController — with Scandit MatrixScan Batch (BarcodeBatch). BarcodeBatch is the right Scandit mode here because it tracks every visible barcode simultaneously on each frame, which matches what these multi-barcode APIs do (a single-scan use case should use BarcodeCapture or SparkScan instead).
Before Anything Else
Read the existing code. Do not ask the user to describe what their scanner does. Identify:
- Which framework is in use (read the imports —
AVFoundation,Vision/VisionKit, a third-party SDK). - Which symbologies are enabled (e.g. the
metadataObjectTypesarray, or the recognized item types). - The result-handling logic: deduplication, accumulation, filtering, per-barcode UI.
- What data models are defined (e.g. a
ScannedBarcodestruct and the collection it feeds). - How the scanner view is presented (embedded, modal, full-screen).
Remove
- The old framework's imports (
import AVFoundation,import VisionKit, etc.). - The capture-session / scanner instance and all its setup (
AVCaptureSession,AVCaptureDeviceInput,AVCaptureMetadataOutput,AVCaptureVideoPreviewLayer, orDataScannerViewController). - The old delegate/callback conformance (
AVCaptureMetadataOutputObjectsDelegateand itsmetadataOutput(_:didOutput:from:), orDataScannerViewControllerDelegate). - The old preview/presentation layer —
BarcodeBatchdraws into aDataCaptureViewinstead.
Integrate MatrixScan Batch
Follow references/integration.md for the full integration. The MatrixScan-Batch-specific points for a migration:
- Map the symbologies. Translate the old type list into
BarcodeBatchSettings. Scandit names differ from Apple's — verify each against the BarcodeBatch API reference rather than guessing. Common AVFoundation mappings:
AVFoundation AVMetadataObject.ObjectType | Scandit Symbology |
|---|---|
.ean13 | .ean13UPCA |
.ean8 | .ean8 |
.code128 | .code128 |
.code39 | .code39 |
.code93 | .code93 |
.qr | .qr |
.pdf417 | .pdf417 |
.dataMatrix | .dataMatrix |
.aztec | .aztec |
.upce | .upce |
.itf14 | .interleavedTwoOfFive |
Note that AVFoundation's .ean13 maps to Scandit .ean13UPCA (EAN-13 and UPC-A share an encoding in Scandit).
- Move the result loop into the listener. AVFoundation reported every barcode in frame on each
metadataOutput(_:didOutput:from:)call; BarcodeBatch reports deltas viabarcodeBatch(_:didUpdate:frameData:). The natural equivalent of "I just saw this barcode" is `session.addedTrackedBarcodes` (the barcodes newly tracked this frame). Read eachTrackedBarcode'sbarcode.dataandbarcode.symbology.
- Respect threading.
metadataOutput(_:didOutput:from:)was delivered on the queue you chose (often.main).barcodeBatch(_:didUpdate:frameData:)always runs on a background queue — copy the data you need out of the session, thenDispatchQueue.main.async {}for any UI or model mutation. Do not hold session-collection references outside the callback.
- Dedup unchanged. If the old code kept a
Setof seen values (or keyed on tracking identity), keep that logic verbatim — feed it fromsession.addedTrackedBarcodesinstead of the AVFoundation metadata objects.
Preserve
- Custom data models (e.g. a
ScannedBarcodestruct) — keep as-is. - The accumulation collection and deduplication logic — move it verbatim into the
barcodeBatch(_:didUpdate:frameData:)flow (dispatched to main). - Any downstream business logic triggered when a barcode is recorded.
After
Show the setup checklist from references/integration.md (SPM packages, NSCameraUsageDescription, license-key placeholder), then a summary with Removed and Added sections listing only what changed. Do not list APIs that were unchanged.
Related skills
FAQ
Is Matrixscan Batch Ios safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.