
Matrixscan Batch Net Ios
- 24 installs
- 17 repo stars
- Updated August 4, 2026
- scandit/skills
Integrate Scandit MatrixScan-style batch barcode capture on.NET iOS using AVCaptureSession patterns from the skill reference implementation.
About
matrixscan-batch-net-ios is a Scandit skills reference for implementing batch barcode scanning on .NET for iOS. The bundled sample centers on AVCaptureSession setup, a full-screen video preview layer, metadata delegate wiring, and accumulating ScannedBarcode records in a list—patterns teams adapt when moving from AVFoundation prototypes to Scandit MatrixScan Batch APIs. Solo builders and small mobile teams use it while building warehouse, retail, or field apps that must scan many codes per session without dropping frames. Prism lists it as integration-oriented procedural knowledge rather than a hosted MCP. Expect to merge this scaffold with official Scandit SDK configuration, licensing, and batch recognition APIs from Scandit documentation; the readme fragment is code-forward and may be incomplete relative to full MatrixScan Batch feature sets.
- UIKit ScannerViewController scaffold with preview layer and result label overlay
- AVCaptureSession lifecycle in ViewWillAppear/ViewWillDisappear start/stop running
- In-memory List<ScannedBarcode> batch collection with Value and Format fields
- MetadataObjectsDelegate hook point for barcode detection pipeline
- C# partial class pattern suitable for .NET iOS (formerly Xamarin) migration to Scandit MatrixScan Batch
Matrixscan Batch Net Ios by the numbers
- 24 all-time installs (skills.sh)
- +5 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #719 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/scandit/skills --skill matrixscan-batch-net-iosAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 24 |
|---|---|
| repo stars | ★ 17 |
| Last updated | August 4, 2026 |
| Repository | scandit/skills ↗ |
What it does
Integrate Scandit MatrixScan-style batch barcode capture on.NET iOS using AVCaptureSession patterns from the skill reference implementation.
Files
MatrixScan Batch .NET for 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 — the class itself was renamed from BarcodeTracking to BarcodeBatch at v7.0, overlay factories evolved, and the .NET binding deviates from the native Swift API in several places (Create factories instead of init(context:settings:), Enabled instead of isEnabled, PascalCase symbology names, DispatchQueue.MainQueue.DispatchAsync for UI dispatch, etc.).
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.
.NET-iOS-specific gotchas worth flagging:
- This skill targets the non-MAUI .NET for iOS workload (project
<TargetFramework>net10.0-ios</TargetFramework>, no<UseMaui>flag). For MAUI apps, use thematrixscan-batch-mauiskill instead (planned). - `BarcodeBatch.Create(dataCaptureContext, settings)` is the .NET factory — not
new BarcodeBatch(...)(the public constructor isprivate) and notBarcodeBatch.ForDataCaptureContext(...)(that name appears in the Swift / docs API but the C# binding isCreate). When the context is non-null, the factory attaches the mode to the context automatically. - `BarcodeBatchSettings.Create()` is a factory — also
privateconstructor. Writingnew BarcodeBatchSettings()is a compile error. - `BarcodeBatchBasicOverlay.Create(...)` and `BarcodeBatchAdvancedOverlay.Create(...)` are factories, each with multiple overloads. When passed a non-null
DataCaptureView, both auto-add the overlay to the view — no separateAddOverlaycall is needed. - `BarcodeBatch.RecommendedCameraSettings` is a static property, not a method. The canonical pattern (mirroring the official .NET iOS sample) is
camera = Camera.GetDefaultCamera(); camera.ApplySettingsAsync(BarcodeBatch.RecommendedCameraSettings);. The Swift formcreateRecommendedCameraSettings()does not exist in the .NET binding. - Camera setup is manual, mirroring BarcodeCapture on .NET iOS, in this order:
Camera.GetDefaultCamera()→dataCaptureContext.SetFrameSourceAsync(camera)→camera.ApplySettingsAsync(BarcodeBatch.RecommendedCameraSettings). Bind the camera to the context before applying settings (matches the officialMatrixScanSimpleSampleorder). The .NET binding isSetFrameSourceAsync— not the synchronous SwiftsetFrameSource(_:completionHandler:). - `DataCaptureView.Create(dataCaptureContext, frame)` takes a `CGRect` as the second argument on iOS — different from the Android
Create(dataCaptureContext)overload. The canonical call site isDataCaptureView.Create(dataCaptureContext, this.View!.Bounds). SetAutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidthand add it withthis.View.AddSubview(dataCaptureView)followed bythis.View.SendSubviewToBack(dataCaptureView). - View-controller constructor depends on how the VC is instantiated. If the VC is inflated by a storyboard / XIB (typical when the project has a
Main.storyboardwithUIMainStoryboardFileset inInfo.plist), keep thepublic MyViewController(IntPtr handle) : base(handle) { }constructor — the runtime calls it with a real native handle. For programmatically-instantiated VCs (noMain.storyboard, root view controller set fromSceneDelegate.WillConnectorAppDelegate), declare a parameterlesspublic MyViewController() : base() { }constructor and instantiate vianew MyViewController(). Do not pass `IntPtr.Zero` to the `(IntPtr)` ctor — that leaves the native peer uninitialized andViewDidLoadmay never fire, which manifests as a black screen with no camera preview and no scans. - `IBarcodeBatchListener.OnSessionUpdated(BarcodeBatch, BarcodeBatchSession, IFrameData)` runs on a background recognition queue — not the main queue. Dispatch any UI work via
DispatchQueue.MainQueue.DispatchAsync(() => { … }). The third parameter isIFrameData(the .NET binding), notFrameData(Swift). - Always call `frameData.Dispose()` at the end of every `OnSessionUpdated` callback (including any early-return path). The official iOS sample explicitly disposes the frame to avoid a "frozen, non-responsive, or severely stuttering" video feed. This is not optional on iOS, and is a difference from the Android skill where the disposal is not required.
- The .NET binding also exposes the event API on
BarcodeBatch:barcodeBatch.SessionUpdated += handler(EventHandler<BarcodeBatchEventArgs>). Use either the listener interface OR the event — not both for the same handler. The event-handler body must still callargs.FrameData.Dispose(). There is noBarcodeScannedevent onBarcodeBatch(batch is tracking, not single-scan). - Do not hold references to `BarcodeBatchSession` or its collections outside `OnSessionUpdated`. The session is only safe to access within that callback — copy
AddedTrackedBarcodes/UpdatedTrackedBarcodes/TrackedBarcodesdata first, then dispatch. BarcodeBatchSessionproperties:AddedTrackedBarcodes(IList<TrackedBarcode>),UpdatedTrackedBarcodes(IList<TrackedBarcode>),RemovedTrackedBarcodes(IList<int>— tracking IDs only, notTrackedBarcode),TrackedBarcodes(IDictionary<int, TrackedBarcode>),FrameSequenceId(long),Reset(). Note:Reset()lives on the Session in the .NET binding (there is noBarcodeBatch.Reset()like the Android Kotlin API has).TrackedBarcodeproperties:Barcode,Identifier(int),Location(Quadrilateral), plusGetAnchorPosition(Anchor). The tracking identifier is reused after a barcode leaves the frame.- The capture mode's enabled property is
barcodeBatch.Enabled(notIsEnabledand not Swift'sisEnabled).IDataCaptureModeexposesEnabledin the .NET binding. - `BarcodeBatchBasicOverlayStyle` is C# PascalCase:
Frame(default) andDot. Notframe/dot(Swift) and notFRAME/DOT(Kotlin). - Per-barcode brush customization (
IBarcodeBatchBasicOverlayListener.BrushForTrackedBarcodeandBarcodeBatchBasicOverlay.SetBrushForTrackedBarcode) requires the MatrixScan AR add-on license. A uniform default brush viaoverlay.Brush = …(no listener) does not require the add-on. - `BarcodeBatchAdvancedOverlay` (anchoring custom
UIViews to tracked barcodes) requires the MatrixScan AR add-on license.IBarcodeBatchAdvancedOverlayListenerhasViewForTrackedBarcode(overlay, trackedBarcode) → UIView?,AnchorForTrackedBarcode(...),OffsetForTrackedBarcode(...)— all three are called on the main thread. The return type isUIView?(the .NET binding mapsViewtoUIKit.UIViewon iOS via a globalusing View = UIKit.UIView;). BarcodeBatchLicenseInfo(read viabarcodeBatch.BarcodeBatchLicenseInfo) isdotnet.ios=8.4+only. Before 8.4 the property does not exist — gate any usage on the installed SDK version. The value is available onceIDataCaptureContextListener.OnModeAddedhas been called.- Symbology names are C# PascalCase:
Symbology.Ean13Upca,Symbology.Ean8,Symbology.Upce,Symbology.Code39,Symbology.Code128,Symbology.InterleavedTwoOfFive,Symbology.Qr,Symbology.DataMatrix. They are not Swift's.ean13UPCA/.code128/.qrstyle. - The NuGet packages are
Scandit.DataCapture.CoreandScandit.DataCapture.Barcode. No separate*.Mauipackages here — those are only for MAUI projects. Do not guess the version from training data — fetch the latest stable fromhttps://www.nuget.org/packages/Scandit.DataCapture.Barcode/viaWebFetchbefore pinning. Inventing a non-existent version causesdotnet restoreto fail withUnable to find package Scandit.DataCapture.Core with version (>= …). Seereferences/integration.mdStep 0 for the full procedure. - iOS `SupportedOSPlatformVersion` must be ≥ `15.0`. Set it in the
.csproj. The officialMatrixScanSimpleSampleInfo.plistMinimumOSVersionis15.0and the project's<SupportedOSPlatformVersion>matches. - The required `Info.plist` key is `NSCameraUsageDescription` (
Privacy - Camera Usage Description). Without it the app crashes on first camera access. iOS prompts the user automatically the first time the camera opens; there is no separate runtime-request API to call (no Android-styleRequestPermissions). - SDK 8.0+ requires explicit initialization. Call
ScanditCaptureCore.Initialize()+ScanditBarcodeCapture.Initialize()inAppDelegate.FinishedLaunchingbefore any Scandit code runs (typically before creating the window / root view controller). Without this the SDK's DI container has no registrations and the firstBarcodeBatch.Create(...)/DataCaptureView.Create(...)call crashes at launch. Not required on 6.x / 7.x. Seereferences/integration.mdStep 0 / Prerequisites for the fullAppDelegatetemplate.
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, anchoring custom UIViews, managing the camera lifecycle, or diagnosing a frozen/stuttering preview (e.g. "add MatrixScan Batch to my .NET iOS app", "scan all barcodes in view at once in C#", "highlight tracked barcodes in green", "anchor a price label to each tracked barcode", "show me how to set up BarcodeBatch in net-ios", "my preview is stuttering after I integrated BarcodeBatch") → read
references/integration.mdand follow the instructions there. - Migrating or upgrading an existing MatrixScan Batch integration (e.g. "upgrade from v6 to v7", "rename BarcodeTracking to BarcodeBatch", "bump the Scandit .NET SDK to v8", "what changed between SDK versions for BarcodeBatch") → read
references/migration.mdand follow the instructions there. - Replacing a third-party multi-barcode scanner with MatrixScan Batch (e.g. "replace my AVFoundation multi-barcode loop with MatrixScan Batch", "migrate from ZXing.Net.Mobile continuous scanning to Scandit", "switch from AVCaptureMetadataOutput to BarcodeBatch") → read
references/third-party-migration.mdand follow the instructions there.
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
Direct users to the right resource based on their question:
| Topic | Resource |
|---|---|
| Get Started | Get Started (.NET for iOS) |
| AR overlays (per-barcode brushes, anchored UIViews) | Adding AR Overlays |
| Migration between major SDK versions | 6 → 7 · 7 → 8 |
| Full API reference | BarcodeBatch API (.NET iOS) |
API surface this skill covers
All classes documented with :available: dotnet.ios in the official RST docs (docs/source/barcode-capture/api/barcode-batch*.rst and api/ui/barcode-batch-*-overlay*.rst) are addressed in references/integration.md:
BarcodeBatch—Create(DataCaptureContext?, BarcodeBatchSettings),Enabled,ApplySettingsAsync(settings),AddListener(IBarcodeBatchListener)/RemoveListener(IBarcodeBatchListener), eventSessionUpdated(EventHandler<BarcodeBatchEventArgs>), staticRecommendedCameraSettings(property, not method),Context,BarcodeBatchLicenseInfo(8.4+),Dispose.BarcodeBatchSettings—Create(),EnableSymbology(Symbology, bool),EnableSymbologies(ICollection<Symbology>),GetSymbologySettings(Symbology),EnabledSymbologies(get),SetProperty/GetProperty<T>/TryGetProperty<T>.BarcodeBatchSession—AddedTrackedBarcodes,UpdatedTrackedBarcodes,RemovedTrackedBarcodes(IList<int>of tracking IDs),TrackedBarcodes(IDictionary<int, TrackedBarcode>),FrameSequenceId,Reset().BarcodeBatchEventArgs—BarcodeBatch,Session,FrameData.IBarcodeBatchListener—OnObservationStarted(BarcodeBatch),OnObservationStopped(BarcodeBatch),OnSessionUpdated(BarcodeBatch, BarcodeBatchSession, IFrameData).BarcodeBatchLicenseInfo(8.4+) —LicensedSymbologies.TrackedBarcode—Barcode,Identifier,Location(Quadrilateral),GetAnchorPosition(Anchor).BarcodeBatchBasicOverlay—Create(barcodeBatch, view, style),Create(barcodeBatch, style),Create(barcodeBatch, view),Create(barcodeBatch),Listener(IBarcodeBatchBasicOverlayListener?),Brush(uniform default brush), staticDefaultBrushForStyle(style),Style(read-only),ShouldShowScanAreaGuides,SetBrushForTrackedBarcode(trackedBarcode, brush),ClearTrackedBarcodeBrushes(),Dispose.BarcodeBatchBasicOverlayStyleenum —Frame,Dot.IBarcodeBatchBasicOverlayListener—BrushForTrackedBarcode(overlay, trackedBarcode),OnTrackedBarcodeTapped(overlay, trackedBarcode). Requires MatrixScan AR add-on.BarcodeBatchAdvancedOverlay—Create(barcodeBatch, view),Create(barcodeBatch),Listener(IBarcodeBatchAdvancedOverlayListener?),SetViewForTrackedBarcode(trackedBarcode, UIView?),SetAnchorForTrackedBarcode(trackedBarcode, anchor),SetOffsetForTrackedBarcode(trackedBarcode, offset),ClearTrackedBarcodeViews(),ShouldShowScanAreaGuides,Dispose. Requires MatrixScan AR add-on.IBarcodeBatchAdvancedOverlayListener—ViewForTrackedBarcode(overlay, trackedBarcode) → UIView?,AnchorForTrackedBarcode(overlay, trackedBarcode),OffsetForTrackedBarcode(overlay, trackedBarcode). Requires MatrixScan AR add-on.
using System.Collections.Generic;
using System.Linq;
using AVFoundation;
using CoreFoundation;
using CoreGraphics;
using Foundation;
using UIKit;
namespace MyApp;
public record ScannedBarcode(string Value, string Format);
public partial class ScannerViewController : UIViewController
{
private UILabel resultLabel = null!;
private AVCaptureSession captureSession = null!;
private AVCaptureVideoPreviewLayer previewLayer = null!;
private MetadataObjectsDelegate metadataDelegate = null!;
private readonly List<ScannedBarcode> scannedBarcodes = new();
public ScannerViewController(IntPtr handle) : base(handle) { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.resultLabel = new UILabel
{
Frame = new CGRect(16, 64, this.View!.Bounds.Width - 32, 44),
TextColor = UIColor.White,
BackgroundColor = UIColor.FromWhiteAlpha(0, 0.4f),
};
this.View.AddSubview(this.resultLabel);
this.SetUpAvFoundationScanner();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (!this.captureSession.Running)
{
this.captureSession.StartRunning();
}
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
if (this.captureSession.Running)
{
this.captureSession.StopRunning();
}
}
private void SetUpAvFoundationScanner()
{
var device = AVCaptureDevice.GetDefaultDevice(AVMediaTypes.Video);
if (device == null) return;
var input = AVCaptureDeviceInput.FromDevice(device, out _);
this.captureSession = new AVCaptureSession();
if (input != null && this.captureSession.CanAddInput(input))
{
this.captureSession.AddInput(input);
}
var metadataOutput = new AVCaptureMetadataOutput();
if (this.captureSession.CanAddOutput(metadataOutput))
{
this.captureSession.AddOutput(metadataOutput);
}
// "Batch" behavior emerges from the per-frame metadata callback +
// a manual dedupe-by-string-value HashSet.
this.metadataDelegate = new MetadataObjectsDelegate(this.HandleBarcodes);
metadataOutput.SetDelegate(this.metadataDelegate, DispatchQueue.MainQueue);
metadataOutput.MetadataObjectTypes =
AVMetadataObjectType.EAN13Code |
AVMetadataObjectType.Code128Code |
AVMetadataObjectType.QRCode;
this.previewLayer = new AVCaptureVideoPreviewLayer(this.captureSession)
{
Frame = this.View!.Bounds,
VideoGravity = AVLayerVideoGravity.ResizeAspectFill,
};
this.View.Layer.AddSublayer(this.previewLayer);
}
private void HandleBarcodes(IList<AVMetadataMachineReadableCodeObject> codes)
{
foreach (var code in codes)
{
if (string.IsNullOrEmpty(code.StringValue)) continue;
var barcode = new ScannedBarcode(code.StringValue, code.Type.ToString());
if (this.scannedBarcodes.Any(b => b.Value == barcode.Value)) continue;
this.scannedBarcodes.Add(barcode);
this.resultLabel.Text =
$"Last scan: {barcode.Value} ({this.scannedBarcodes.Count} total)";
}
}
private sealed class MetadataObjectsDelegate : AVCaptureMetadataOutputObjectsDelegate
{
private readonly Action<IList<AVMetadataMachineReadableCodeObject>> handler;
public MetadataObjectsDelegate(
Action<IList<AVMetadataMachineReadableCodeObject>> handler)
{
this.handler = handler;
}
public override void DidOutputMetadataObjects(
AVCaptureMetadataOutput captureOutput,
AVMetadataObject[] metadataObjects,
AVCaptureConnection connection)
{
var codes = metadataObjects
.OfType<AVMetadataMachineReadableCodeObject>()
.ToList();
this.handler(codes);
}
}
}
using Foundation;
using UIKit;
namespace MyApp;
public partial class ViewController : UIViewController
{
public ViewController(IntPtr handle) : base(handle) { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
}
}
using System.Collections.Generic;
using System.Linq;
using CoreFoundation;
using Foundation;
using UIKit;
using Scandit.DataCapture.Barcode.Batch.Capture;
using Scandit.DataCapture.Barcode.Batch.Data;
using Scandit.DataCapture.Barcode.Batch.UI.Overlay;
using Scandit.DataCapture.Barcode.Data;
using Scandit.DataCapture.Core.Capture;
using Scandit.DataCapture.Core.Data;
using Scandit.DataCapture.Core.Source;
using Scandit.DataCapture.Core.UI;
namespace MyApp;
public partial class ViewController : UIViewController, IBarcodeBatchListener
{
public const string SCANDIT_LICENSE_KEY = "-- ENTER YOUR SCANDIT LICENSE KEY HERE --";
private DataCaptureContext dataCaptureContext = null!;
private BarcodeBatch barcodeBatch = null!;
private Camera? camera;
private readonly HashSet<string> scannedData = new();
public ViewController(IntPtr handle) : base(handle) { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.InitializeAndStartBatchScanning();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.barcodeBatch.Enabled = true;
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.On);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.barcodeBatch.Enabled = false;
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.Off);
}
private void InitializeAndStartBatchScanning()
{
this.dataCaptureContext = DataCaptureContext.ForLicenseKey(SCANDIT_LICENSE_KEY);
this.camera = Camera.GetDefaultCamera();
if (this.camera != null)
{
CameraSettings cameraSettings = BarcodeBatch.RecommendedCameraSettings;
cameraSettings.PreferredResolution = VideoResolution.FullHd;
this.camera.ApplySettingsAsync(cameraSettings);
this.dataCaptureContext.SetFrameSourceAsync(this.camera);
}
BarcodeBatchSettings settings = BarcodeBatchSettings.Create();
settings.EnableSymbologies(new HashSet<Symbology>
{
Symbology.Ean13Upca,
Symbology.Code128,
});
this.barcodeBatch = BarcodeBatch.Create(this.dataCaptureContext, settings);
this.barcodeBatch.AddListener(this);
var dataCaptureView = DataCaptureView.Create(this.dataCaptureContext, this.View!.Bounds);
UIView platformView = dataCaptureView;
platformView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight |
UIViewAutoresizing.FlexibleWidth;
this.View.AddSubview(dataCaptureView);
this.View.SendSubviewToBack(dataCaptureView);
BarcodeBatchBasicOverlay.Create(
this.barcodeBatch,
dataCaptureView,
BarcodeBatchBasicOverlayStyle.Frame);
}
public void OnSessionUpdated(
BarcodeBatch barcodeBatch,
BarcodeBatchSession session,
IFrameData frameData)
{
try
{
var addedData = session.AddedTrackedBarcodes
.Select(tb => tb.Barcode.Data)
.Where(d => d != null)
.Cast<string>()
.ToList();
DispatchQueue.MainQueue.DispatchAsync(() =>
{
foreach (var data in addedData)
{
this.scannedData.Add(data);
}
});
}
finally
{
frameData.Dispose();
}
}
public void OnObservationStarted(BarcodeBatch barcodeBatch) { }
public void OnObservationStopped(BarcodeBatch barcodeBatch) { }
}
using System.Collections.Generic;
using System.Linq;
using CoreFoundation;
using Foundation;
using UIKit;
using Scandit.DataCapture.Barcode.Data;
using Scandit.DataCapture.Barcode.Tracking.Capture;
using Scandit.DataCapture.Barcode.Tracking.Data;
using Scandit.DataCapture.Barcode.Tracking.UI.Overlay;
using Scandit.DataCapture.Core.Capture;
using Scandit.DataCapture.Core.Data;
using Scandit.DataCapture.Core.Source;
using Scandit.DataCapture.Core.UI;
namespace MyApp;
public partial class ViewController : UIViewController, IBarcodeTrackingListener
{
public const string SCANDIT_LICENSE_KEY = "-- ENTER YOUR SCANDIT LICENSE KEY HERE --";
private DataCaptureContext dataCaptureContext = null!;
private BarcodeTracking barcodeTracking = null!;
private Camera? camera;
public ViewController(IntPtr handle) : base(handle) { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.InitializeAndStartTracking();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.barcodeTracking.Enabled = true;
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.On);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.barcodeTracking.Enabled = false;
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.Off);
}
private void InitializeAndStartTracking()
{
this.dataCaptureContext = DataCaptureContext.ForLicenseKey(SCANDIT_LICENSE_KEY);
// v6 pattern: hand-rolled CameraSettings instead of BarcodeTracking.RecommendedCameraSettings.
var cameraSettings = new CameraSettings { PreferredResolution = VideoResolution.FullHd };
this.camera = Camera.GetDefaultCamera();
if (this.camera != null)
{
this.camera.ApplySettingsAsync(cameraSettings);
this.dataCaptureContext.SetFrameSourceAsync(this.camera);
}
BarcodeTrackingSettings settings = BarcodeTrackingSettings.Create();
settings.EnableSymbology(Symbology.Ean13Upca, true);
settings.EnableSymbology(Symbology.Code128, true);
this.barcodeTracking = BarcodeTracking.Create(this.dataCaptureContext, settings);
this.barcodeTracking.AddListener(this);
var dataCaptureView = DataCaptureView.Create(this.dataCaptureContext, this.View!.Bounds);
UIView platformView = dataCaptureView;
platformView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight |
UIViewAutoresizing.FlexibleWidth;
this.View.AddSubview(dataCaptureView);
this.View.SendSubviewToBack(dataCaptureView);
BarcodeTrackingBasicOverlay.Create(this.barcodeTracking, dataCaptureView);
}
public void OnSessionUpdated(
BarcodeTracking barcodeTracking,
BarcodeTrackingSession session,
IFrameData frameData)
{
try
{
var addedData = session.AddedTrackedBarcodes
.Select(tb => tb.Barcode.Data)
.ToList();
DispatchQueue.MainQueue.DispatchAsync(() =>
{
foreach (var data in addedData)
{
// handle data
}
});
}
finally
{
frameData.Dispose();
}
}
public void OnObservationStarted(BarcodeTracking barcodeTracking) { }
public void OnObservationStopped(BarcodeTracking barcodeTracking) { }
}
{
"skill_name": "matrixscan-batch-net-ios",
"evals": [
{
"id": 1,
"prompt": "I want to add MatrixScan Batch to my .NET for iOS app. Here's my empty UIViewController: EmptyViewController.cs. I need to scan EAN-13 and Code 128 barcodes \u2014 every visible barcode should be tracked and added to a list as soon as it appears on screen.",
"expected_output": "The skill reads integration.md, fetches the latest Scandit NuGet versions, writes complete BarcodeBatch integration code into EmptyViewController.cs (DataCaptureContext.ForLicenseKey, Camera.GetDefaultCamera, BarcodeBatch.RecommendedCameraSettings, BarcodeBatchSettings.Create with Symbology.Ean13Upca + Symbology.Code128, BarcodeBatch.Create, DataCaptureView.Create(context, this.View!.Bounds) added via AddSubview + SendSubviewToBack with AutoresizingMask, BarcodeBatchBasicOverlay.Create, IBarcodeBatchListener with OnSessionUpdated dispatching to DispatchQueue.MainQueue and wrapping in try/finally with frameData.Dispose(), ViewWillAppear/ViewWillDisappear lifecycle), and shows the setup checklist (NuGet packages, NSCameraUsageDescription in Info.plist, AppDelegate Initialize for SDK 8.0+, SupportedOSPlatformVersion 15.0, license key).",
"files": [
"fixtures/EmptyViewController.cs"
],
"assertions": [
{"text": "Setup checklist is shown and mentions adding the Scandit.DataCapture.Barcode and Scandit.DataCapture.Core NuGet packages"},
{"text": "Setup checklist mentions adding NSCameraUsageDescription to Info.plist"},
{"text": "Setup checklist mentions calling ScanditCaptureCore.Initialize() and ScanditBarcodeCapture.Initialize() in AppDelegate.FinishedLaunching (required for SDK 8.0+)"},
{"text": "Setup checklist mentions SupportedOSPlatformVersion of 15.0 or higher in the .csproj"},
{"text": "A Scandit license key placeholder string is present"},
{"text": "DataCaptureContext.ForLicenseKey( is used to create the context"},
{"text": "BarcodeBatchSettings.Create() is used (not new BarcodeBatchSettings(), which does not exist in the .NET binding)"},
{"text": "BarcodeBatch.Create( is used (not new BarcodeBatch(...) and not BarcodeBatch.ForDataCaptureContext(...))"},
{"text": "Symbology.Ean13Upca is enabled"},
{"text": "Symbology.Code128 is enabled"},
{"text": "No symbologies other than Ean13Upca and Code128 are enabled"},
{"text": "Camera.GetDefaultCamera() is used"},
{"text": "BarcodeBatch.RecommendedCameraSettings is used as a static property (not BarcodeBatch.RecommendedCameraSettings() with parentheses and not createRecommendedCameraSettings())"},
{"text": "camera.ApplySettingsAsync(...) is called with the recommended camera settings"},
{"text": "dataCaptureContext.SetFrameSourceAsync(camera) is called"},
{"text": "DataCaptureView.Create(dataCaptureContext, this.View!.Bounds) (or this.View.Bounds) is used \u2014 the second argument is a CGRect / bounds (the iOS overload, not the Android single-arg overload)"},
{"text": "AutoresizingMask is set to UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth on the DataCaptureView"},
{"text": "The DataCaptureView is added via this.View.AddSubview(dataCaptureView)"},
{"text": "this.View.SendSubviewToBack(dataCaptureView) is called so the preview sits behind any UI chrome"},
{"text": "BarcodeBatchBasicOverlay.Create( is called and given both the BarcodeBatch and the DataCaptureView arguments"},
{"text": "The view controller implements IBarcodeBatchListener (or subscribes to barcodeBatch.SessionUpdated +=)"},
{"text": "OnSessionUpdated is implemented with the signature (BarcodeBatch, BarcodeBatchSession, IFrameData) \u2014 not (BarcodeBatch, BarcodeBatchSession, FrameData)"},
{"text": "Inside OnSessionUpdated, data from session.AddedTrackedBarcodes is copied out before scheduling UI work"},
{"text": "UI work is dispatched via DispatchQueue.MainQueue.DispatchAsync(() => ...)"},
{"text": "frameData.Dispose() is called inside OnSessionUpdated, ideally in a finally block so it runs on every code path"},
{"text": "ViewWillAppear sets barcodeBatch.Enabled = true and calls camera.SwitchToDesiredStateAsync(FrameSourceState.On)"},
{"text": "ViewWillDisappear sets barcodeBatch.Enabled = false and calls camera.SwitchToDesiredStateAsync(FrameSourceState.Off)"},
{"text": "No Android lifecycle hooks (OnResume / OnPause / OnDestroy / RunOnUiThread / RequestPermissions / FindViewById / FrameLayout / AppCompatActivity) appear"},
{"text": "No BarcodeCapture.Create or IBarcodeCaptureListener appears (this is MatrixScan Batch, not single-shot BarcodeCapture)"},
{"text": "No Swift-style symbology names (e.g. .ean13UPCA, .code128) appear"},
{"text": "No SparkScan-style new SparkScan(...) constructor appears"}
]
},
{
"id": 2,
"prompt": "I want to highlight EAN-13 barcodes in green and CODE128 barcodes in blue in my MatrixScan Batch ViewController. Here is my ViewController: IntegratedViewController.cs.",
"expected_output": "The skill implements IBarcodeBatchBasicOverlayListener, overrides BrushForTrackedBarcode to return colored Scandit Brushes per symbology (using UIColor + Scandit.DataCapture.Core.UI.Style.Brush), and assigns the listener to the overlay. The existing integration code (BarcodeBatch.Create, DataCaptureView.Create, BarcodeBatchBasicOverlay.Create, BarcodeBatchListener, camera lifecycle, frameData.Dispose()) is preserved.",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "The class declares it implements IBarcodeBatchBasicOverlayListener"},
{"text": "BrushForTrackedBarcode(BarcodeBatchBasicOverlay overlay, TrackedBarcode trackedBarcode) is implemented"},
{"text": "EAN-13 (Symbology.Ean13Upca) returns a green Scandit Brush"},
{"text": "CODE128 (Symbology.Code128) returns a blue Scandit Brush"},
{"text": "The overlay's Listener property is assigned to this (or to an instance of the listener)"},
{"text": "The Scandit Brush type is used (Scandit.DataCapture.Core.UI.Style.Brush), constructed with UIColor (not Android.Graphics.Color or Android.Graphics.Paint)"},
{"text": "Existing BarcodeBatch.Create call is preserved (not rewritten as new BarcodeBatch)"},
{"text": "Existing IBarcodeBatchListener implementation (OnSessionUpdated, OnObservationStarted, OnObservationStopped) is preserved"},
{"text": "Existing frameData.Dispose() call inside OnSessionUpdated is preserved"},
{"text": "Existing camera lifecycle in ViewWillAppear / ViewWillDisappear is preserved"}
]
},
{
"id": 3,
"prompt": "In my MatrixScan Batch ViewController I want to log all currently tracked barcodes \u2014 their tracking ID and data \u2014 on every frame. Here is my ViewController: IntegratedViewController.cs.",
"expected_output": "The skill updates OnSessionUpdated to iterate over session.TrackedBarcodes and log each TrackedBarcode's Identifier and Barcode.Data. Data must be copied out of the session before DispatchQueue.MainQueue.DispatchAsync, because the session is only safe to access on the recognition queue. frameData.Dispose() in a finally block is preserved.",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "session.TrackedBarcodes (the IDictionary<int, TrackedBarcode>) is accessed inside OnSessionUpdated"},
{"text": "Each TrackedBarcode's Identifier is read"},
{"text": "Each TrackedBarcode's Barcode.Data is read"},
{"text": "The identifier+data pairs are copied into a local collection before DispatchQueue.MainQueue.DispatchAsync is called (session.TrackedBarcodes is not accessed inside the main-queue dispatch lambda)"},
{"text": "A logging call (Console.WriteLine, System.Diagnostics.Debug.WriteLine, NSLog, or similar) is used to emit each tracked barcode"},
{"text": "frameData.Dispose() is still called inside OnSessionUpdated, ideally in a finally block"},
{"text": "Existing BarcodeBatch.Create / Camera / DataCaptureView / BarcodeBatchBasicOverlay setup is preserved"},
{"text": "Existing ViewWillAppear / ViewWillDisappear lifecycle is preserved"}
]
},
{
"id": 4,
"prompt": "In my MatrixScan Batch ViewController I want to anchor a small custom label (a UILabel showing the barcode's data) on top of each tracked barcode, and have it move with the barcode as the camera pans. I have the MatrixScan AR add-on. Here is my ViewController: IntegratedViewController.cs.",
"expected_output": "The skill adds a BarcodeBatchAdvancedOverlay: it creates the overlay with BarcodeBatchAdvancedOverlay.Create(barcodeBatch, dataCaptureView), makes the ViewController implement IBarcodeBatchAdvancedOverlayListener, assigns the overlay's Listener property, and implements ViewForTrackedBarcode to return a UILabel whose Text is the tracked barcode's data. It notes the advanced overlay requires the MatrixScan AR add-on and that ViewForTrackedBarcode is called on the main thread. The existing BarcodeBatchBasicOverlay, IBarcodeBatchListener (with its frameData.Dispose() in a finally block), camera setup, and ViewWillAppear / ViewWillDisappear lifecycle are all preserved.",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "BarcodeBatchAdvancedOverlay.Create( is called with the BarcodeBatch and the DataCaptureView (not new BarcodeBatchAdvancedOverlay(...))"},
{"text": "The class declares it implements IBarcodeBatchAdvancedOverlayListener"},
{"text": "The advanced overlay's Listener property is assigned (advancedOverlay.Listener = this)"},
{"text": "ViewForTrackedBarcode(BarcodeBatchAdvancedOverlay overlay, TrackedBarcode trackedBarcode) is implemented"},
{"text": "ViewForTrackedBarcode returns a UIView (e.g. a UILabel) — not an Android View / TextView"},
{"text": "The returned UILabel's Text is set from trackedBarcode.Barcode.Data"},
{"text": "The using Scandit.DataCapture.Barcode.Batch.UI.Overlay; directive is present"},
{"text": "The answer notes the advanced overlay requires the MatrixScan AR add-on (semantic)"},
{"text": "Existing BarcodeBatchBasicOverlay.Create call is preserved — the advanced overlay is added in addition, not as a replacement (semantic)"},
{"text": "Existing IBarcodeBatchListener.OnSessionUpdated implementation is preserved, including its frameData.Dispose() in a finally block"},
{"text": "Existing ViewWillAppear / ViewWillDisappear camera lifecycle is preserved"},
{"text": "No Android view types (TextView, FrameLayout, Android.Views.View) and no Android lifecycle hooks (OnResume / OnPause / OnDestroy) appear"}
],
"tags": ["advanced-overlay"]
},
{
"id": 5,
"prompt": "I'm already anchoring a UILabel to each tracked barcode with BarcodeBatchAdvancedOverlay in my MatrixScan Batch ViewController. Now I want the label to sit centered just above each barcode rather than overlapping it. Here is my ViewController: IntegratedViewController.cs.",
"expected_output": "The skill implements AnchorForTrackedBarcode to return Anchor.TopCenter and OffsetForTrackedBarcode to return a PointWithUnit built from two FloatWithUnit values using MeasureUnit.Fraction, with a negative vertical fraction to push the label above the barcode. It explains that the offset is relative to the anchor and that a fractional unit scales with the view size. The existing ViewForTrackedBarcode, advanced-overlay setup, and frameData.Dispose() integration code are preserved.",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "AnchorForTrackedBarcode(BarcodeBatchAdvancedOverlay overlay, TrackedBarcode trackedBarcode) is implemented and returns Anchor.TopCenter"},
{"text": "OffsetForTrackedBarcode(BarcodeBatchAdvancedOverlay overlay, TrackedBarcode trackedBarcode) is implemented and returns a PointWithUnit"},
{"text": "The PointWithUnit is constructed from two FloatWithUnit values (x and y)"},
{"text": "Each FloatWithUnit uses MeasureUnit.Fraction as its unit"},
{"text": "The vertical (y) FloatWithUnit uses a negative value to position the label above the barcode"},
{"text": "The anchor value is Anchor.TopCenter (semantic) — not a string and not a lowercase Swift/Kotlin-style token like .topCenter"},
{"text": "The using Scandit.DataCapture.Core.Common.Geometry; directive is present for Anchor, PointWithUnit, FloatWithUnit and MeasureUnit"},
{"text": "Existing ViewForTrackedBarcode / BarcodeBatchAdvancedOverlay setup is preserved"},
{"text": "Existing frameData.Dispose() in the OnSessionUpdated finally block is preserved"}
],
"tags": ["advanced-overlay-positioning"]
},
{
"id": 6,
"prompt": "In my MatrixScan Batch ViewController I want the device to beep and vibrate whenever a new barcode starts being tracked. Here is my ViewController: IntegratedViewController.cs.",
"expected_output": "The skill explains that BarcodeBatch (unlike SparkScan or single-shot BarcodeCapture) has NO automatic feedback, so feedback must be emitted manually. It creates a Feedback instance once — held as a field via Feedback.DefaultFeedback or new Feedback(Vibration.DefaultVibration, Sound.DefaultSound) — using the Scandit.DataCapture.Core.Common.Feedback namespace, and inside OnSessionUpdated calls feedback.Emit() only for barcodes newly tracked this frame (driven off session.AddedTrackedBarcodes). The existing frameData.Dispose() in the finally block, BarcodeBatch.Create / camera / DataCaptureView / BarcodeBatchBasicOverlay setup, and ViewWillAppear / ViewWillDisappear lifecycle are preserved.",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "A Scandit Feedback instance is created via Feedback.DefaultFeedback (a static property, not Feedback.DefaultFeedback() with parentheses) or via the new Feedback(Vibration.DefaultVibration, Sound.DefaultSound) constructor"},
{"text": "The Feedback instance is created once (e.g. as a field), not allocated inside OnSessionUpdated on every frame"},
{"text": "feedback.Emit() is called to play the sound and emit the vibration"},
{"text": "Emit() is called inside OnSessionUpdated, driven off session.AddedTrackedBarcodes (the barcodes newly tracked this frame) so it fires only for new barcodes, not on every frame"},
{"text": "The using Scandit.DataCapture.Core.Common.Feedback; directive is present (the namespace for Feedback, Vibration and Sound)"},
{"text": "The answer states that BarcodeBatch has no automatic feedback and that feedback must be emitted manually (semantic)"},
{"text": "No feedback property is assumed on BarcodeBatch or BarcodeBatchSettings (BarcodeBatch exposes no feedback setting — it is emitted manually) (semantic)"},
{"text": "No BarcodeCaptureFeedback or SparkScanFeedback type is used (those belong to other modes, not BarcodeBatch) (semantic)"},
{"text": "frameData.Dispose() is still called inside OnSessionUpdated, ideally in a finally block"},
{"text": "Existing BarcodeBatch.Create / Camera / DataCaptureView / BarcodeBatchBasicOverlay setup is preserved"},
{"text": "Existing ViewWillAppear / ViewWillDisappear lifecycle is preserved"}
],
"tags": ["feedback"]
}
]
}
{
"skill_name": "matrixscan-batch-net-ios",
"evals": [
{
"id": 1,
"prompt": "I need to upgrade my .NET for iOS MatrixScan app from Scandit SDK v6 to v7. Here is my current ViewController: ViewControllerV6.cs.",
"expected_output": "The skill renames every v6 BarcodeTracking* type to its v7 BarcodeBatch* equivalent (class names, settings, listener interface, session, basic overlay), updates the Scandit.DataCapture.Barcode.Tracking.* using directives to Scandit.DataCapture.Barcode.Batch.*, and modernizes the camera setup to use BarcodeBatch.RecommendedCameraSettings (the static property) instead of a hand-rolled CameraSettings. The .NET factory method name (Create) is unchanged. Provides the v6\u2192v7 migration guide URL.",
"files": [
"fixtures/ViewControllerV6.cs"
],
"assertions": [
{"text": "All BarcodeTracking class references are renamed to BarcodeBatch"},
{"text": "BarcodeTrackingSettings is renamed to BarcodeBatchSettings"},
{"text": "BarcodeTrackingSession is renamed to BarcodeBatchSession"},
{"text": "IBarcodeTrackingListener is renamed to IBarcodeBatchListener"},
{"text": "BarcodeTrackingBasicOverlay is renamed to BarcodeBatchBasicOverlay"},
{"text": "using Scandit.DataCapture.Barcode.Tracking.Capture is renamed to using Scandit.DataCapture.Barcode.Batch.Capture"},
{"text": "using Scandit.DataCapture.Barcode.Tracking.Data is renamed to using Scandit.DataCapture.Barcode.Batch.Data"},
{"text": "using Scandit.DataCapture.Barcode.Tracking.UI.Overlay is renamed to using Scandit.DataCapture.Barcode.Batch.UI.Overlay"},
{"text": "Camera setup is updated to use BarcodeBatch.RecommendedCameraSettings (the v6 hand-rolled new CameraSettings is removed or replaced)"},
{"text": "BarcodeBatch.RecommendedCameraSettings is read as a static property, not a method call with parentheses"},
{"text": "The factory call shape Create(dataCaptureContext, settings) is preserved (not renamed to ForDataCaptureContext)"},
{"text": "Existing iOS lifecycle (ViewWillAppear / ViewWillDisappear) is preserved \u2014 no Android OnResume/OnPause appears"},
{"text": "Existing frameData.Dispose() inside the listener callback is preserved through the rename"},
{"text": "The .NET v6\u2192v7 migration guide URL https://docs.scandit.com/sdks/net/ios/migrate-6-to-7/ is provided"},
{"text": "No BarcodeTracking-named identifier remains in the rewritten file"}
]
},
{
"id": 2,
"prompt": "I'm upgrading my Scandit .NET iOS MatrixScan Batch SDK from v7 to v8. What do I need to change in my code?",
"expected_output": "The skill explains that v8 requires explicit SDK initialization in AppDelegate.FinishedLaunching \u2014 ScanditCaptureCore.Initialize() and ScanditBarcodeCapture.Initialize() must be called before any Scandit type is constructed. The BarcodeBatch factory pattern (BarcodeBatch.Create(context, settings)) and the listener/session APIs are unchanged. Provides the v7\u2192v8 migration guide URL.",
"files": [],
"assertions": [
{"text": "The skill explains that SDK 8.0+ requires explicit initialization via ScanditCaptureCore.Initialize() and ScanditBarcodeCapture.Initialize()"},
{"text": "The skill instructs the user to add the two Initialize() calls to AppDelegate.FinishedLaunching (or the existing application:didFinishLaunchingWithOptions handler)"},
{"text": "The initialization calls go into AppDelegate.FinishedLaunching, NOT into a MainApplication subclass (MainApplication is an Android-only concept)"},
{"text": "The skill does NOT instruct the user to replace BarcodeBatch.Create(context, settings) with anything else \u2014 the .NET BarcodeBatch factory is unchanged in v7\u2192v8"},
{"text": "The skill does NOT instruct the user to rename Create(...) to ForDataCaptureContext(...) (Create is the .NET name across versions)"},
{"text": "Migration guide URL https://docs.scandit.com/sdks/net/ios/migrate-7-to-8/ is provided"}
]
}
]
}
{
"skill_name": "matrixscan-batch-net-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 and accumulated into a dedupe-by-tracking-id list. Here is my ViewController: AVFoundationViewController.cs.",
"expected_output": "The skill removes AVFoundation references (the AVCaptureSession / AVCaptureDevice / AVCaptureMetadataOutput / AVCaptureVideoPreviewLayer setup, the IAVCaptureMetadataOutputObjectsDelegate / AVCaptureMetadataOutputObjectsDelegate metadata callback, the AVMetadataObjectType masks) and replaces them with MatrixScan Batch: DataCaptureContext.ForLicenseKey, manual camera setup via Camera.GetDefaultCamera + BarcodeBatch.RecommendedCameraSettings + SetFrameSourceAsync, BarcodeBatchSettings.Create with Ean13Upca + Code128 + Qr, BarcodeBatch.Create, DataCaptureView.Create(context, this.View!.Bounds) added via AddSubview + SendSubviewToBack with AutoresizingMask, BarcodeBatchBasicOverlay.Create, IBarcodeBatchListener with OnSessionUpdated reading session.AddedTrackedBarcodes (dispatched to DispatchQueue.MainQueue), and frameData.Dispose() in a finally block. The existing ScannedBarcode record and the dedupe logic is preserved. The skill provides the setup checklist (NuGet packages, NSCameraUsageDescription, AppDelegate initialize for SDK 8.0+).",
"files": [
"fixtures/AVFoundationViewController.cs"
],
"assertions": [
{"text": "The AVCaptureSession / AVCaptureDevice / AVCaptureDeviceInput setup is removed"},
{"text": "The AVCaptureMetadataOutput instance and its SetDelegate(...) wiring are removed"},
{"text": "The AVCaptureMetadataOutputObjectsDelegate (or IAVCaptureMetadataOutputObjectsDelegate) inner class and its DidOutputMetadataObjects override are removed"},
{"text": "The AVCaptureVideoPreviewLayer setup is removed (replaced by DataCaptureView)"},
{"text": "No AVMetadataObjectType enum usage remains (EAN13Code, Code128Code, QRCode)"},
{"text": "DataCaptureContext.ForLicenseKey( is used to create the context"},
{"text": "BarcodeBatchSettings.Create() is used"},
{"text": "BarcodeBatch.Create( is used as the factory"},
{"text": "Symbology.Ean13Upca is enabled (mapped from AVMetadataObjectType.EAN13Code)"},
{"text": "Symbology.Code128 is enabled (mapped from AVMetadataObjectType.Code128Code)"},
{"text": "Symbology.Qr is enabled (mapped from AVMetadataObjectType.QRCode \u2014 not Symbology.QrCode)"},
{"text": "Camera.GetDefaultCamera() is used"},
{"text": "BarcodeBatch.RecommendedCameraSettings is used as a static property"},
{"text": "dataCaptureContext.SetFrameSourceAsync(camera) is called"},
{"text": "DataCaptureView.Create(dataCaptureContext, this.View!.Bounds) (or this.View.Bounds) is used \u2014 the iOS CGRect overload"},
{"text": "The DataCaptureView is added via this.View.AddSubview(...) (replacing the old AVCaptureVideoPreviewLayer.AddSublayer)"},
{"text": "BarcodeBatchBasicOverlay.Create( is called with both the BarcodeBatch and the DataCaptureView"},
{"text": "The view controller implements IBarcodeBatchListener (or subscribes to barcodeBatch.SessionUpdated +=)"},
{"text": "OnSessionUpdated iterates session.AddedTrackedBarcodes (the new-this-frame collection)"},
{"text": "The handler dispatches UI updates via DispatchQueue.MainQueue.DispatchAsync (because OnSessionUpdated runs on a background recognition queue)"},
{"text": "frameData.Dispose() is called inside OnSessionUpdated, ideally in a finally block"},
{"text": "The existing ScannedBarcode record (or equivalent data model) is preserved"},
{"text": "Dedupe logic (using either tracking Identifier or the barcode Value) is preserved in the new flow"},
{"text": "Setup checklist is shown and mentions adding the Scandit.DataCapture.Barcode and Scandit.DataCapture.Core NuGet packages"},
{"text": "Setup checklist mentions adding NSCameraUsageDescription to Info.plist"}
]
}
]
}
MatrixScan Batch .NET for iOS Integration Guide
BarcodeBatch is the multi-barcode tracking mode. It simultaneously tracks every barcode 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. Camera and lifecycle are managed manually, exactly like BarcodeCapture on .NET iOS.
Examples below use C# 12 and a UIViewController. The same APIs work in storyboards, XIBs, or programmatically-instantiated controllers — adapt ownership of DataCaptureContext, BarcodeBatch, and the Camera to the project's existing structure.
Constructor pattern depends on instantiation path. Storyboard / XIB inflation usespublic MyVC(IntPtr handle) : base(handle) { }. Programmatic instantiation (noMain.storyboard, root view controller set fromSceneDelegate.WillConnectorAppDelegate) needs a parameterlesspublic MyVC() : base() { }andnew MyVC(). Never construct a VC with `new MyVC(IntPtr.Zero)` — the native peer is not initialized,ViewDidLoadmay never fire, and you'll see a black screen with no preview and no scans. If you support both paths, declare both constructors.
MAUI? Stop. If the project file has<UseMaui>true</UseMaui>, switch to thematrixscan-batch-mauiskill. The MAUI integration uses XAML and aUseScanditBarcodebuilder, which is different.
Prerequisites
Step 0 — Fetch the latest SDK version from NuGet (mandatory, do this before any edits)
Before editing the .csproj, WebFetch https://www.nuget.org/packages/Scandit.DataCapture.Barcode/ and read the latest stable version number off the page (skip -beta.* / -preview.* / -rc.* suffixes). Use that exact version for both packages.
Do not guess, do not reuse a version from training data, and do not invent a number like 8.13.0 when only 8.4.0 is the latest stable — dotnet restore will fail with Unable to find package Scandit.DataCapture.Core with version (>= 8.13.0). The latest stable version changes regularly; only the live NuGet page is authoritative. If WebFetch fails, fall back to https://api.nuget.org/v3-flatcontainer/scandit.datacapture.barcode/index.json (last entry without a pre-release suffix) before proceeding.
MatrixScan Batch on dotnet.ios was first published in 6.16. Anything older does not have a BarcodeBatch API on this platform.Other prerequisites
- Scandit Data Capture SDK for .NET — add both packages to the
.csproj, pinned to the version fetched in Step 0:
<ItemGroup>
<PackageReference Include="Scandit.DataCapture.Core" Version="<step-0-version>" />
<PackageReference Include="Scandit.DataCapture.Barcode" Version="<step-0-version>" />
</ItemGroup>Both packages are published on NuGet.org. Do not add Scandit.DataCapture.Core.Maui or Scandit.DataCapture.Barcode.Maui — those are MAUI-only.
- 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.
- Camera usage description in `Info.plist`:
<key>NSCameraUsageDescription</key>
<string>Used to scan barcodes.</string>Without this key the app crashes on first camera access. iOS prompts the user for permission automatically the first time the camera is opened; there is no separate runtime-request API to call (the Scandit SDK triggers the standard system prompt when the camera starts). This is different from .NET for Android, which needs a manual RequestPermissions call.
- `SupportedOSPlatformVersion` must be at least `15.0` in the
.csproj:
<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>Matches the official MatrixScanSimpleSample Info.plist MinimumOSVersion.
- SDK initialization (Scandit 8.0+). Initialize the Scandit DI container in
AppDelegate.FinishedLaunchingbefore any Scandit type is constructed. Without this the firstDataCaptureContext.ForLicenseKey(...)/BarcodeBatch.Create(...)/DataCaptureView.Create(...)call crashes because the container has no registrations.
using Foundation;
using Scandit.DataCapture.Barcode;
using Scandit.DataCapture.Core;
using UIKit;
namespace MyApp;
[Register("AppDelegate")]
public class AppDelegate : UIResponder, IUIApplicationDelegate
{
[Export("window")]
public UIWindow? Window { get; set; }
[Export("application:didFinishLaunchingWithOptions:")]
public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
ScanditCaptureCore.Initialize();
ScanditBarcodeCapture.Initialize();
return true;
}
}If the project already has an AppDelegate, add the two Initialize() calls at the top of FinishedLaunching rather than creating a second delegate. This step is only required on Scandit SDK 8.0+ — earlier majors (6.x, 7.x) self-initialized, so for those versions skip this entirely.
Project scaffolding (new projects only)
If a .NET iOS project already exists, skip this subsection and go to the Integration flow below.
Recommended: scaffold a buildable shell with the official template, then add MatrixScan Batch on top:
dotnet new ios -o MyApp
cd MyAppThis produces a project with the correct OutputType, a working AppDelegate / SceneDelegate, an Info.plist, and storyboard-or-programmatic UI scaffolding. Add the Scandit packages from the bullets above, bump <SupportedOSPlatformVersion> to 15.0 (or higher), add NSCameraUsageDescription to Info.plist, and continue with Step 1.
Integration flow
Ask the user which barcode symbologies they need to scan. When asking, mention that enabling only the symbologies they actually need improves tracking performance and accuracy.
Once the user responds, ask them which UIViewController 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 <PackageReference Include="Scandit.DataCapture.Barcode" Version="<step-0-version>" /> and <PackageReference Include="Scandit.DataCapture.Core" Version="<step-0-version>" /> to the .csproj (use the version pinned in Step 0 above — do not guess). 2. Add NSCameraUsageDescription to Info.plist with a short user-facing description. 3. If targeting SDK 8.0+, ensure AppDelegate.FinishedLaunching calls ScanditCaptureCore.Initialize() and ScanditBarcodeCapture.Initialize() before constructing any Scandit type. 4. Ensure <SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion> (or higher) is set in the .csproj. 5. Replace -- ENTER YOUR SCANDIT LICENSE KEY HERE -- with your key from https://ssl.scandit.com.
Namespaces
| Class | Namespace |
|---|---|
BarcodeBatch, BarcodeBatchSettings, IBarcodeBatchListener, BarcodeBatchSession, BarcodeBatchEventArgs, BarcodeBatchLicenseInfo | Scandit.DataCapture.Barcode.Batch.Capture |
BarcodeBatchBasicOverlay, BarcodeBatchBasicOverlayStyle, IBarcodeBatchBasicOverlayListener | Scandit.DataCapture.Barcode.Batch.UI.Overlay |
BarcodeBatchAdvancedOverlay, IBarcodeBatchAdvancedOverlayListener | Scandit.DataCapture.Barcode.Batch.UI.Overlay |
TrackedBarcode | Scandit.DataCapture.Barcode.Batch.Data |
Symbology, Barcode, SymbologyDescription | Scandit.DataCapture.Barcode.Data |
DataCaptureContext | Scandit.DataCapture.Core.Capture |
Camera, FrameSourceState, VideoResolution | Scandit.DataCapture.Core.Source |
DataCaptureView | Scandit.DataCapture.Core.UI |
IFrameData | Scandit.DataCapture.Core.Data |
Brush | Scandit.DataCapture.Core.UI.Style |
Anchor, PointWithUnit, Quadrilateral, FloatWithUnit, MeasureUnit | Scandit.DataCapture.Core.Common.Geometry |
Step 1 — Create the DataCaptureContext
The DataCaptureContext is the central hub of the SDK. Construct it once and reuse the same reference for the lifetime of the scanning surface.
using Scandit.DataCapture.Core.Capture;
private DataCaptureContext dataCaptureContext =
DataCaptureContext.ForLicenseKey("-- ENTER YOUR SCANDIT LICENSE KEY HERE --");Step 2 — Configure BarcodeBatchSettings
All symbologies are disabled by default. Enable each one explicitly; enabling only what is needed reduces tracking overhead.
using Scandit.DataCapture.Barcode.Batch.Capture;
using Scandit.DataCapture.Barcode.Data;
BarcodeBatchSettings settings = BarcodeBatchSettings.Create();
settings.EnableSymbology(Symbology.Ean13Upca, true);
settings.EnableSymbology(Symbology.Ean8, true);
settings.EnableSymbology(Symbology.Code128, true);You can also enable a set of symbologies at once:
settings.EnableSymbologies(new HashSet<Symbology>
{
Symbology.Ean13Upca,
Symbology.Code128
});BarcodeBatchSettings members
| Member | Description |
|---|---|
BarcodeBatchSettings.Create() (static factory) | Constructs a new settings instance with all symbologies disabled. There is no public constructor — always use Create(). |
EnableSymbology(Symbology, bool) | Enable or disable a single symbology. |
EnableSymbologies(ICollection<Symbology>) | Enable a set in one call (a HashSet<Symbology> is the idiomatic argument). |
GetSymbologySettings(Symbology) | Returns the per-symbology SymbologySettings (e.g. ActiveSymbolCounts as ICollection<short>). |
EnabledSymbologies (get) | Currently enabled symbologies (ICollection<Symbology>). |
SetProperty(string, object) / GetProperty(string) / GetProperty<T>(string) / TryGetProperty<T>(string, out T?) | Read/write unstable/experimental engine flags. |
Symbology names are C# PascalCase. The full set includesEan13Upca,Ean8,Upce,Code39,Code93,Code128,InterleavedTwoOfFive,Qr,DataMatrix,Pdf417,Aztec,Codabar, and more. Don't use Swift-style camelCase names (ean13UPCA).
Step 3 — Camera setup
Camera.GetDefaultCamera() returns the back camera. The canonical pattern (matching the official .NET iOS MatrixScanSimpleSample) is to obtain the camera, attach it as the frame source first, then apply BarcodeBatch.RecommendedCameraSettings via ApplySettingsAsync, and drive it from ViewWillAppear / ViewWillDisappear.
Order matters.SetFrameSourceAsync(camera)must be called beforecamera.ApplySettingsAsync(cameraSettings). This matches the official sample. Reversing the order can leave the preview blank.
using Scandit.DataCapture.Core.Source;
private Camera? camera;
private void SetUpCamera()
{
this.camera = Camera.GetDefaultCamera();
if (this.camera != null)
{
// 1. Bind the camera to the context FIRST.
this.dataCaptureContext.SetFrameSourceAsync(this.camera);
// 2. Then apply camera settings.
// BarcodeBatch.RecommendedCameraSettings is a static PROPERTY — not a method.
// The Swift form `recommendedCameraSettings` is a class var; the .NET binding
// exposes it as a static property here.
CameraSettings cameraSettings = BarcodeBatch.RecommendedCameraSettings;
// The official iOS sample bumps to Full HD for better decode range.
cameraSettings.PreferredResolution = VideoResolution.FullHd;
this.camera.ApplySettingsAsync(cameraSettings);
}
}Switch the camera on / off:
await this.camera?.SwitchToDesiredStateAsync(FrameSourceState.On); // start preview / tracking
await this.camera?.SwitchToDesiredStateAsync(FrameSourceState.Off); // release the cameraCamera.GetCamera(CameraPosition.WorldFacing)also returns the back camera explicitly.GetDefaultCamera()returns the recommended camera for the device.
Step 4 — Create the BarcodeBatch mode
this.barcodeBatch = BarcodeBatch.Create(this.dataCaptureContext, settings);Create(context, settings) is the factory — the constructor is private. When the context argument is non-null, the mode is automatically added to the context (you do not need a separate dataCaptureContext.SetMode(...) call). Passing null for the context creates a detached mode, which you can later attach by passing it to a context.
Re-applying settings at runtime:
await this.barcodeBatch.ApplySettingsAsync(newSettings);BarcodeBatch members
| Member | Description |
|---|---|
BarcodeBatch.Create(DataCaptureContext?, BarcodeBatchSettings) | Factory — creates the mode. Attaches to the context when context is non-null. |
Enabled (bool get/set) | Pause / resume tracking without tearing down the camera. |
ApplySettingsAsync(BarcodeBatchSettings) (Task) | Apply new settings on the next processed frame. |
AddListener(IBarcodeBatchListener) / RemoveListener(IBarcodeBatchListener) | Register or remove a listener. |
event EventHandler<BarcodeBatchEventArgs> SessionUpdated | C# event raised every processed frame. Equivalent to IBarcodeBatchListener.OnSessionUpdated. |
static RecommendedCameraSettings (CameraSettings get) | Recommended CameraSettings for BarcodeBatch. Static property, not a method. |
Context (DataCaptureContext? get) | The context the mode is attached to. |
BarcodeBatchLicenseInfo (BarcodeBatchLicenseInfo? get) | Licensed symbologies. Available from 8.4+ on `dotnet.ios`. Value is populated after IDataCaptureContextListener.OnModeAdded. |
Dispose() | Releases native resources. |
Use eitherAddListeneror theSessionUpdatedevent — not both for the same handler. There is noBarcodeScannedevent onBarcodeBatch(batch is tracking, not single-scan).
Step 5 — DataCaptureView
DataCaptureView.Create(dataCaptureContext, frame) creates the camera preview as a UIView. The iOS overload takes a CGRect (typically this.View.Bounds) as the second argument — different from the Android Create(dataCaptureContext) form.
using Scandit.DataCapture.Core.UI;
using UIKit;
// In ViewDidLoad / InitializeAndStartBatchScanning, after this.View exists:
var dataCaptureView = DataCaptureView.Create(this.dataCaptureContext, this.View!.Bounds);
UIView platformView = dataCaptureView;
platformView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight |
UIViewAutoresizing.FlexibleWidth;
this.View.AddSubview(dataCaptureView);
this.View.SendSubviewToBack(dataCaptureView);DataCaptureViewis aUIViewand is not auto-attached to any parent. CallAddSubviewexplicitly, thenSendSubviewToBackso any other subviews (UI chrome, buttons) sit on top of the camera preview. SettingAutoresizingMaskensures the preview resizes correctly on rotation and split-screen.
Step 6 — BarcodeBatchBasicOverlay
BarcodeBatchBasicOverlay renders a highlight frame or dot over each tracked barcode. The Create(barcodeBatch, dataCaptureView, ...) factory auto-adds the overlay to the view — no separate AddOverlay call needed.
using Scandit.DataCapture.Barcode.Batch.UI.Overlay;
// Default style (Frame):
BarcodeBatchBasicOverlay overlay =
BarcodeBatchBasicOverlay.Create(this.barcodeBatch, dataCaptureView);
// Or choose a style explicitly:
BarcodeBatchBasicOverlay overlay = BarcodeBatchBasicOverlay.Create(
this.barcodeBatch,
dataCaptureView,
BarcodeBatchBasicOverlayStyle.Frame);BarcodeBatchBasicOverlay members
| Member | Description |
|---|---|
Create(BarcodeBatch, DataCaptureView?, BarcodeBatchBasicOverlayStyle) | Factory — creates the overlay with a specific style and adds it to the view when non-null. |
Create(BarcodeBatch, DataCaptureView?) | Factory — same, with default Frame style. |
Create(BarcodeBatch, BarcodeBatchBasicOverlayStyle) | Factory — creates the overlay detached from a view. |
Create(BarcodeBatch) | Factory — creates the overlay with default Frame style, detached from a view. |
Listener (IBarcodeBatchBasicOverlayListener? get/set) | For per-barcode brush customization. Requires MatrixScan AR add-on. |
Brush (Brush? get/set) | Uniform brush applied to all tracked barcodes when no listener is set. Setting to null hides every tracked barcode. |
static DefaultBrushForStyle(BarcodeBatchBasicOverlayStyle) | Returns the default Scandit brush for that style. |
Style (BarcodeBatchBasicOverlayStyle get, read-only) | The overlay style passed to Create. |
ShouldShowScanAreaGuides (bool get/set) | Debug aid: show the active scan-area outline. Defaults to false. |
SetBrushForTrackedBarcode(TrackedBarcode, Brush?) | Imperatively set the brush for a specific tracked barcode. Requires MatrixScan AR add-on. |
ClearTrackedBarcodeBrushes() | Clears all imperatively-set brushes. |
Dispose() | Releases native resources. |
BarcodeBatchBasicOverlayStyle enum
| Value | Description |
|---|---|
Frame | Draws highlights as a rectangular frame, with an appearance animation when a code is newly tracked. Default. |
Dot | Draws highlights as a dot, with an appearance animation. |
Per-barcode brush customization (requires MatrixScan AR add-on)
Implement IBarcodeBatchBasicOverlayListener to return a different brush per barcode. BrushForTrackedBarcode is called from the rendering thread.
using UIKit;
using Scandit.DataCapture.Barcode.Batch.UI.Overlay;
using Scandit.DataCapture.Barcode.Batch.Data;
using Scandit.DataCapture.Barcode.Data;
using Brush = Scandit.DataCapture.Core.UI.Style.Brush;
public partial class BatchScanViewController : UIViewController,
IBarcodeBatchListener,
IBarcodeBatchBasicOverlayListener
{
// ... other fields / setup ...
public Brush? BrushForTrackedBarcode(BarcodeBatchBasicOverlay overlay, TrackedBarcode trackedBarcode)
{
// Return null to use the overlay's default brush.
// Return a fully transparent brush to hide the barcode highlight.
// UIColor.FromRGBA on iOS takes normalized floats in the 0.0..1.0 range
// (the official Scandit iOS samples use the `x / 255f` pattern).
return trackedBarcode.Barcode.Symbology switch
{
Symbology.Ean13Upca => new Brush(
fillColor: UIColor.Green.ColorWithAlpha(0.4f),
strokeColor: UIColor.Green,
strokeWidth: 2f),
_ => null,
};
}
public void OnTrackedBarcodeTapped(BarcodeBatchBasicOverlay overlay, TrackedBarcode trackedBarcode)
{
// React to the user tapping a barcode highlight.
}
}Assign the listener after creating the overlay:
overlay.Listener = this;MatrixScan AR add-on required forBrushForTrackedBarcodeandSetBrushForTrackedBarcode. A uniform default brush (no listener) does not require the add-on.
Step 7 — IBarcodeBatchListener (or subscribe to SessionUpdated)
Implement IBarcodeBatchListener to receive per-frame session updates. OnSessionUpdated is called on a background recognition queue — do not hold session references outside the callback, dispatch any UI work via DispatchQueue.MainQueue.DispatchAsync, and always call `frameData.Dispose()` before returning (failing to dispose causes a frozen / stuttering preview).
Listener interface (parity with the Swift / Android native APIs)
using CoreFoundation;
using Scandit.DataCapture.Barcode.Batch.Capture;
using Scandit.DataCapture.Barcode.Batch.Data;
using Scandit.DataCapture.Core.Data;
public partial class BatchScanViewController : UIViewController, IBarcodeBatchListener
{
public void OnSessionUpdated(
BarcodeBatch barcodeBatch,
BarcodeBatchSession session,
IFrameData frameData)
{
try
{
// Called on a background recognition queue. Copy the data you need…
var addedData = session.AddedTrackedBarcodes
.Select(tb => tb.Barcode.Data)
.Where(d => d != null)
.Cast<string>()
.ToList();
// …then dispatch UI updates onto the main queue.
DispatchQueue.MainQueue.DispatchAsync(() =>
{
foreach (var data in addedData)
{
// handle data
}
});
}
finally
{
// Always dispose the frame, including on the early-return / exception path.
// Failing to dispose causes a frozen, non-responsive, or stuttering preview.
frameData.Dispose();
}
}
public void OnObservationStarted(BarcodeBatch barcodeBatch) { }
public void OnObservationStopped(BarcodeBatch barcodeBatch) { }
}
// In InitializeAndStartBatchScanning:
this.barcodeBatch.AddListener(this);Event handler (idiomatic C#)
this.barcodeBatch.SessionUpdated += (sender, args) =>
{
try
{
var addedData = args.Session.AddedTrackedBarcodes
.Select(tb => tb.Barcode.Data)
.ToList();
DispatchQueue.MainQueue.DispatchAsync(() =>
{
foreach (var data in addedData)
{
// handle data
}
});
}
finally
{
args.FrameData.Dispose();
}
};IBarcodeBatchListener
| Callback | Description |
|---|---|
OnSessionUpdated(BarcodeBatch, BarcodeBatchSession, IFrameData) | Called every processed frame. Background recognition queue. Copy data, dispatch UI work via DispatchQueue.MainQueue.DispatchAsync, and dispose the frame. |
OnObservationStarted(BarcodeBatch) | Listener was registered. |
OnObservationStopped(BarcodeBatch) | Listener was removed. |
BarcodeBatchEventArgs (for the event-based API)
| Member | Type | Description |
|---|---|---|
BarcodeBatch | BarcodeBatch | The mode that raised the event. |
Session | BarcodeBatchSession | The active session. |
FrameData | IFrameData | The frame that produced the event. Always call .Dispose() on this before the handler returns. |
BarcodeBatchSession
| Property / Method | Type | Description |
|---|---|---|
AddedTrackedBarcodes | IList<TrackedBarcode> | Barcodes newly tracked in this frame. |
UpdatedTrackedBarcodes | IList<TrackedBarcode> | Barcodes whose position changed in this frame. |
RemovedTrackedBarcodes | IList<int> | Tracking IDs of barcodes that left the view (not TrackedBarcode instances). |
TrackedBarcodes | IDictionary<int, TrackedBarcode> | All currently tracked barcodes, keyed by tracking ID. |
FrameSequenceId | long | Identifier of the current frame sequence. |
Reset() | method | Clear all tracked state. Only call from inside OnSessionUpdated. |
Important: Do not hold references toBarcodeBatchSessionor its collections outsideOnSessionUpdated. Copy any data you need before the callback returns. The session is mutated by the recognition thread on the next frame.
TrackedBarcode
| Property / Method | Type | Description |
|---|---|---|
Barcode | 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. |
GetAnchorPosition(Anchor) | Point | Returns the position of the given anchor on the tracked barcode. |
Step 8 — Lifecycle management
Drive the camera and the Enabled flag from ViewWillAppear and ViewWillDisappear. The camera must not be active while the view controller is not visible.
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
// Resume processing frames.
this.barcodeBatch.Enabled = true;
// Switch the camera on. iOS asks for the camera permission automatically on
// the first launch when NSCameraUsageDescription is set in Info.plist.
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.On);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
// Stop processing frames.
this.barcodeBatch.Enabled = false;
// Switch the camera off to release it.
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.Off);
}For explicit teardown (e.g. when the scanning view controller is being deallocated), remove the listener and detach the mode:
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.barcodeBatch.RemoveListener(this);
this.dataCaptureContext.RemoveCurrentMode();
}
base.Dispose(disposing);
}Unlike Android (OnResume/OnPause/OnDestroy), iOS lifecycle hooks areViewWillAppear/ViewWillDisappear(orViewDidAppear/ViewDidDisappear). No runtime permission check is needed — iOS handles that via theNSCameraUsageDescriptionplist key the first time the camera opens.
Complete minimal example
This mirrors the structure of the official MatrixScanSimpleSample for .NET iOS.
using System.Collections.Generic;
using System.Linq;
using CoreFoundation;
using Foundation;
using UIKit;
using Scandit.DataCapture.Barcode.Batch.Capture;
using Scandit.DataCapture.Barcode.Batch.Data;
using Scandit.DataCapture.Barcode.Batch.UI.Overlay;
using Scandit.DataCapture.Barcode.Data;
using Scandit.DataCapture.Core.Capture;
using Scandit.DataCapture.Core.Data;
using Scandit.DataCapture.Core.Source;
using Scandit.DataCapture.Core.UI;
namespace MyApp;
public partial class BatchScanViewController : UIViewController, IBarcodeBatchListener
{
public const string SCANDIT_LICENSE_KEY = "-- ENTER YOUR SCANDIT LICENSE KEY HERE --";
private DataCaptureContext dataCaptureContext = null!;
private BarcodeBatch barcodeBatch = null!;
private Camera? camera;
private readonly HashSet<string> scannedData = new();
// Storyboard-loaded VCs: keep this constructor (the runtime calls it with a real handle).
// Programmatically-instantiated VCs (no Main.storyboard): use the parameterless ctor below
// and `new BatchScanViewController()`. DO NOT call `new BatchScanViewController(IntPtr.Zero)` —
// that leaves the native peer uninitialized and ViewDidLoad may never fire.
public BatchScanViewController(IntPtr handle) : base(handle) { }
public BatchScanViewController() : base() { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.InitializeAndStartBatchScanning();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.barcodeBatch.Enabled = true;
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.On);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.barcodeBatch.Enabled = false;
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.Off);
}
private void InitializeAndStartBatchScanning()
{
this.dataCaptureContext = DataCaptureContext.ForLicenseKey(SCANDIT_LICENSE_KEY);
this.camera = Camera.GetDefaultCamera();
if (this.camera != null)
{
// Bind the camera to the context BEFORE applying settings — matches the
// official MatrixScanSimpleSample order. Reversing these leaves the preview blank.
this.dataCaptureContext.SetFrameSourceAsync(this.camera);
CameraSettings cameraSettings = BarcodeBatch.RecommendedCameraSettings;
cameraSettings.PreferredResolution = VideoResolution.FullHd;
this.camera.ApplySettingsAsync(cameraSettings);
}
BarcodeBatchSettings settings = BarcodeBatchSettings.Create();
settings.EnableSymbologies(new HashSet<Symbology>
{
Symbology.Ean13Upca,
Symbology.Code128,
});
this.barcodeBatch = BarcodeBatch.Create(this.dataCaptureContext, settings);
this.barcodeBatch.AddListener(this);
var dataCaptureView = DataCaptureView.Create(this.dataCaptureContext, this.View!.Bounds);
UIView platformView = dataCaptureView;
platformView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight |
UIViewAutoresizing.FlexibleWidth;
this.View.AddSubview(dataCaptureView);
this.View.SendSubviewToBack(dataCaptureView);
BarcodeBatchBasicOverlay.Create(
this.barcodeBatch,
dataCaptureView,
BarcodeBatchBasicOverlayStyle.Frame);
}
public void OnSessionUpdated(
BarcodeBatch barcodeBatch,
BarcodeBatchSession session,
IFrameData frameData)
{
try
{
// Copy the data we need off the recognition queue before dispatching.
var addedData = session.AddedTrackedBarcodes
.Select(tb => tb.Barcode.Data)
.Where(d => d != null)
.Cast<string>()
.ToList();
DispatchQueue.MainQueue.DispatchAsync(() =>
{
foreach (var data in addedData)
{
this.scannedData.Add(data);
}
});
}
finally
{
frameData.Dispose();
}
}
public void OnObservationStarted(BarcodeBatch barcodeBatch) { }
public void OnObservationStopped(BarcodeBatch barcodeBatch) { }
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.barcodeBatch?.RemoveListener(this);
}
base.Dispose(disposing);
}
}The officialMatrixScanSimpleSampledoes not overrideDispose— it relies on the framework's deterministic teardown. Removing the listener inDispose(bool)is a safe belt-and-suspenders for VCs that may be recreated. Do not call `dataCaptureContext.RemoveCurrentMode()` here: it can race with the recognition queue and tear down the mode while a frame is still in flight.
Optional: BarcodeBatchAdvancedOverlay (requires MatrixScan AR add-on)
BarcodeBatchAdvancedOverlay anchors a custom UIView to each tracked barcode in real time, retaining its relative position as the barcode moves. The Create factory auto-adds the overlay to the view when given a non-null DataCaptureView.
using UIKit;
using Scandit.DataCapture.Barcode.Batch.UI.Overlay;
using Scandit.DataCapture.Barcode.Batch.Data;
using Scandit.DataCapture.Core.Common.Geometry;
public partial class BatchScanViewController : UIViewController,
IBarcodeBatchListener,
IBarcodeBatchAdvancedOverlayListener
{
private BarcodeBatchAdvancedOverlay advancedOverlay = null!;
// In InitializeAndStartBatchScanning, after creating dataCaptureView:
private void SetUpAdvancedOverlay(DataCaptureView dataCaptureView)
{
this.advancedOverlay = BarcodeBatchAdvancedOverlay.Create(
this.barcodeBatch,
dataCaptureView);
this.advancedOverlay.Listener = this;
}
// Called on the main thread for each tracked barcode.
// Return a UIView to anchor to this barcode, or null to show nothing.
public UIView? ViewForTrackedBarcode(
BarcodeBatchAdvancedOverlay overlay,
TrackedBarcode trackedBarcode)
{
var label = new UILabel
{
Text = trackedBarcode.Barcode.Data,
TextColor = UIColor.Black,
BackgroundColor = UIColor.White,
TextAlignment = UITextAlignment.Center,
};
label.SizeToFit();
// Add a little padding around the text.
label.Frame = new CoreGraphics.CGRect(
label.Frame.X, label.Frame.Y,
label.Frame.Width + 16, label.Frame.Height + 8);
return label;
}
public Anchor AnchorForTrackedBarcode(
BarcodeBatchAdvancedOverlay overlay,
TrackedBarcode trackedBarcode) => Anchor.TopCenter;
public PointWithUnit OffsetForTrackedBarcode(
BarcodeBatchAdvancedOverlay overlay,
TrackedBarcode trackedBarcode) =>
new PointWithUnit(
new FloatWithUnit(0f, MeasureUnit.Fraction),
new FloatWithUnit(-1f, MeasureUnit.Fraction));
}To update the view for a specific tracked barcode imperatively (e.g. after an async lookup completes), call the Set*ForTrackedBarcode methods. They are thread-safe.
this.advancedOverlay.SetViewForTrackedBarcode(trackedBarcode, updatedView);
this.advancedOverlay.SetAnchorForTrackedBarcode(trackedBarcode, Anchor.TopCenter);
this.advancedOverlay.SetOffsetForTrackedBarcode(trackedBarcode, offset);
this.advancedOverlay.ClearTrackedBarcodeViews(); // remove all viewsBarcodeBatchAdvancedOverlay members
| Member | Description |
|---|---|
Create(BarcodeBatch, DataCaptureView?) | Factory — creates the overlay and adds it to the view when non-null. |
Create(BarcodeBatch) | Factory — creates a detached overlay; attach later by passing it to a DataCaptureView. |
Listener (IBarcodeBatchAdvancedOverlayListener? get/set) | Per-barcode view / anchor / offset provider. |
SetViewForTrackedBarcode(TrackedBarcode, UIView?) | Set or update the UIView for a barcode. Pass null to remove. Thread-safe. |
SetAnchorForTrackedBarcode(TrackedBarcode, Anchor) | Override the anchor for a barcode. Thread-safe. |
SetOffsetForTrackedBarcode(TrackedBarcode, PointWithUnit) | Override the offset for a barcode. Thread-safe. |
ClearTrackedBarcodeViews() | Remove all anchored views. Thread-safe. |
ShouldShowScanAreaGuides (bool get/set) | Debug: show the active scan-area outline. |
Dispose() | Releases native resources. |
IBarcodeBatchAdvancedOverlayListener
| Callback | Description |
|---|---|
ViewForTrackedBarcode(overlay, trackedBarcode) → UIView? | Return the UIView to anchor to this barcode, or null for none. Called on the main thread. |
AnchorForTrackedBarcode(overlay, trackedBarcode) → Anchor | Return the anchor for this barcode's view (e.g. Anchor.TopCenter). |
OffsetForTrackedBarcode(overlay, trackedBarcode) → PointWithUnit | Return a PointWithUnit offset to fine-tune the view position. |
For tap callbacks and additional advanced-overlay options, fetch the Adding AR Overlays page.
Optional: feedback (beep / vibration on a newly tracked barcode)
Unlike single-shot BarcodeCapture — which owns a BarcodeCaptureFeedback and beeps automatically on every accepted scan — and unlike SparkScan, `BarcodeBatch` has no built-in feedback and emits nothing on its own. There is no feedback property on BarcodeBatch or BarcodeBatchSettings. Tracking many codes per frame would otherwise produce a continuous buzz. To beep or vibrate when a code is first tracked, own a Feedback instance yourself and call Emit() from OnSessionUpdated, gated on session.AddedTrackedBarcodes (the barcodes newly tracked this frame).
session.AddedTrackedBarcodes is the right trigger: it is the set added this frame, so it does not re-fire while a code stays in view. If a tracking ID can be reused after a code leaves and re-enters the frame, de-duplicate against a HashSet<int> of identifiers you have already signalled.
using System.Linq;
using Scandit.DataCapture.Core.Common.Feedback;
// Build the Feedback once (as a field) and reuse it; do NOT allocate per frame.
// new Feedback(vibration, sound) — pass null for either component to omit it.
private readonly Feedback feedback =
new Feedback(Vibration.DefaultVibration, Sound.DefaultSound);
// (or simply: private readonly Feedback feedback = Feedback.DefaultFeedback;)
// Identifiers already signalled, so feedback fires once per newly tracked barcode.
private readonly HashSet<int> signalledTrackingIds = new();
public void OnSessionUpdated(
BarcodeBatch barcodeBatch,
BarcodeBatchSession session,
IFrameData frameData)
{
try
{
// Add() returns true only the first time an identifier is seen, so Emit()
// fires once per newly tracked barcode rather than on every frame.
bool firstSighting = session.AddedTrackedBarcodes
.Any(tb => this.signalledTrackingIds.Add(tb.Identifier));
if (firstSighting)
{
this.feedback.Emit();
}
}
finally
{
frameData.Dispose();
}
}Feedback.Emit() is influenced by the device ring mode and volume settings, so a correctly configured Sound/Vibration may still be silent on a muted device. Members live in Scandit.DataCapture.Core.Common.Feedback:
| Member | Type | Description |
|---|---|---|
new Feedback(Vibration?, Sound?) | ctor | Feedback with both components; pass null to omit one. Also new Feedback(Vibration?) and new Feedback(Sound?). |
Feedback.DefaultFeedback | static Feedback (property, no ()) | A feedback with the default sound and default vibration (the same beep + vibration single-shot capture uses). |
Vibration.DefaultVibration | static Vibration (property) | The default success vibration. Vibration.SuccessHapticFeedback and Vibration.SelectionHapticFeedback are also available on iOS. |
Sound.DefaultSound | static Sound (property) | The default success beep. |
feedback.Emit() | void | Plays the sound and emits the vibration defined by the instance. |
Feedback,VibrationandSoundareIDisposablein the .NET binding. Hold theFeedbackfor the controller's lifetime and let it be released with the controller; do not allocate a new one per frame.
Optional: pause / reset tracking
| Action | How |
|---|---|
| Pause tracking without releasing the camera | barcodeBatch.Enabled = false |
| Resume tracking | barcodeBatch.Enabled = true |
| Reset the tracker (clear all tracked barcodes) | Inside OnSessionUpdated, call session.Reset(). Do not access `session` outside the callback. |
Optional: BarcodeBatchLicenseInfo (8.4+)
Once the mode has been attached to the context and the context has emitted OnModeAdded, you can inspect which symbologies the active license allows:
using Scandit.DataCapture.Barcode.Batch.Capture;
// After IDataCaptureContextListener.OnModeAdded fires:
BarcodeBatchLicenseInfo? licenseInfo = this.barcodeBatch.BarcodeBatchLicenseInfo;
ICollection<Symbology>? licensed = licenseInfo?.LicensedSymbologies;BarcodeBatchLicenseInfo is available from Scandit dotnet.ios 8.4 onwards. On earlier versions the property does not exist.
Troubleshooting: frozen, non-responsive, or stuttering preview
Symptom: After integrating BarcodeBatch the camera preview freezes after a few frames, becomes unresponsive, or starts stuttering badly. Tracking updates stop arriving.
Cause: IFrameData (the frameData parameter of OnSessionUpdated, or args.FrameData from the SessionUpdated event) holds onto a native frame buffer. The .NET-iOS binding requires the consumer to explicitly Dispose() it; otherwise the recognition pipeline runs out of buffers and stalls.
Fix: Wrap the body of every OnSessionUpdated callback in a try { ... } finally { frameData.Dispose(); }, so the frame is always disposed — even on the early-return / exception path:
public void OnSessionUpdated(BarcodeBatch barcodeBatch, BarcodeBatchSession session, IFrameData frameData)
{
try
{
// … handle session …
}
finally
{
frameData.Dispose();
}
}The same applies to the event-based API — call args.FrameData.Dispose() in a finally. This is iOS-specific; on Android the binding manages the frame's lifetime for you.
Key rules
1. One context per scanning surface — construct DataCaptureContext.ForLicenseKey(key) once and reuse it. 2. Factory, not constructor — BarcodeBatch.Create(context, settings) is the factory. Both new BarcodeBatch(...) and BarcodeBatch.ForDataCaptureContext(...) are compile errors in the .NET binding. 3. Settings factory too — BarcodeBatchSettings.Create() is the factory; new BarcodeBatchSettings() is a compile error. 4. Manual camera, in this order — Camera.GetDefaultCamera() → dataCaptureContext.SetFrameSourceAsync(camera) → camera.ApplySettingsAsync(BarcodeBatch.RecommendedCameraSettings). Bind the camera to the context before applying settings (matches the official MatrixScanSimpleSample); reversing the order can leave the preview blank. RecommendedCameraSettings is a static property, not a method. 5. DataCaptureView takes a CGRect on iOS — DataCaptureView.Create(dataCaptureContext, this.View!.Bounds), then AutoresizingMask, AddSubview, SendSubviewToBack. 6. Recognition queue — OnSessionUpdated runs on a background queue. Copy the data you need, then dispatch UI work via DispatchQueue.MainQueue.DispatchAsync(() => …). 7. Always dispose `IFrameData` — every OnSessionUpdated (and every SessionUpdated event handler) must call frameData.Dispose() in a finally block. Missing this is the #1 cause of frozen / stuttering previews on iOS. 8. Don't retain the session — the session and its collections are only safe within OnSessionUpdated. Copy data out before the callback returns. 9. Overlay auto-adds — BarcodeBatchBasicOverlay.Create(mode, view, ...) and BarcodeBatchAdvancedOverlay.Create(mode, view) both add themselves to the DataCaptureView automatically when view is non-null. 10. AR add-on gates — per-barcode brush customization (IBarcodeBatchBasicOverlayListener / SetBrushForTrackedBarcode) and BarcodeBatchAdvancedOverlay both require the MatrixScan AR add-on license. 11. `Enabled` for pause/resume — toggle barcodeBatch.Enabled to pause and resume tracking without removing the mode or releasing the camera. 12. Lifecycle cleanup — turn the camera off in ViewWillDisappear(), back on in ViewWillAppear(). If overriding Dispose(bool), call barcodeBatch?.RemoveListener(this) only. Do not call dataCaptureContext.RemoveCurrentMode() from Dispose — it can race with the recognition queue and tear the mode down while a frame is still being processed. The official MatrixScanSimpleSample does not override Dispose at all. 13. Symbologies — all disabled by default; enable only what is needed. Names are PascalCase (Ean13Upca, not .ean13UPCA). 14. No runtime permission call — iOS handles the camera prompt automatically once NSCameraUsageDescription is in Info.plist. There is no Android-style RequestPermissions. 15. SDK 8.0+ initialization — AppDelegate.FinishedLaunching calling ScanditCaptureCore.Initialize() + ScanditBarcodeCapture.Initialize() is mandatory on 8.0+. 16. `IFrameData`, not `FrameData` — the .NET listener signature passes an IFrameData. Don't import FrameData (that's a Swift type). 17. No automatic feedback — BarcodeBatch has no built-in beep/vibration and exposes no feedback property (unlike BarcodeCaptureFeedback / SparkScanFeedback). To signal a new code, own a Feedback (Feedback.DefaultFeedback or new Feedback(Vibration.DefaultVibration, Sound.DefaultSound)) and call feedback.Emit() from OnSessionUpdated, gated on session.AddedTrackedBarcodes. Feedback.DefaultFeedback / Vibration.DefaultVibration / Sound.DefaultSound are static properties, not methods.
MatrixScan Batch .NET for 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 .csproj (and Directory.Packages.props if Central Package Management is in use) for the <PackageReference Include="Scandit.DataCapture.Barcode" Version="..." /> (or Scandit.DataCapture.Core) line. Both packages should be pinned to the same version. If they drift, treat the lowest version as the installed one.
Once you know the installed version, determine which migration path applies:
| Installed version | Target version | Action |
|---|---|---|
6.x (dotnet.ios >= 6.16) | 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 the version, ask the user which version they are migrating from.
Note: MatrixScan Batch ondotnet.ioswas first published in 6.16 (a brief window during the 6 line where the class was still namedBarcodeTracking). The cross-platform rename toBarcodeBatchlanded at 7.0. Anything older than 6.16 does not have this API ondotnet.ios— confirm with the user before assuming a version below 6.16.
---
Step 2: Update the dependency version
Before touching source files, WebFetch https://www.nuget.org/packages/Scandit.DataCapture.Barcode/ and read the latest stable version. Then update the SDK version in every <PackageReference>:
Scandit.DataCapture.CoreScandit.DataCapture.Barcode
Do not guess. The latest stable version changes regularly; only the live NuGet page is authoritative. If WebFetch fails, fall back to https://api.nuget.org/v3-flatcontainer/scandit.datacapture.barcode/index.json (last entry without a pre-release suffix).
Restore packages (dotnet restore or rebuild in the IDE) before continuing.
---
Step 3: Apply source-code changes
Search for files that use MatrixScan Batch (search for BarcodeBatch, BarcodeBatchSettings, BarcodeBatchSession, BarcodeBatchEventArgs, IBarcodeBatchListener, BarcodeBatchBasicOverlay, BarcodeBatchAdvancedOverlay, and also the v6 names BarcodeTracking, BarcodeTrackingSettings, BarcodeTrackingSession, IBarcodeTrackingListener, BarcodeTrackingBasicOverlay, BarcodeTrackingAdvancedOverlay) and apply the relevant changes below directly to those files.
---
Migration: 6 → 7
The 6→7 step for .NET iOS MatrixScan Batch is primarily about the cross-platform BarcodeTracking → BarcodeBatch rename and the new namespace under Batch.*. Go through every section below and apply each change that matches the project.
BarcodeTracking → BarcodeBatch rename
The class family was renamed at v7.0 across all platforms (iOS, Android, .NET, Flutter, etc.). Apply this rename everywhere the old names appear:
| Old (v6) | New (v7) |
|---|---|
BarcodeTracking | BarcodeBatch |
BarcodeTrackingSettings | BarcodeBatchSettings |
BarcodeTrackingSession | BarcodeBatchSession |
BarcodeTrackingEventArgs | BarcodeBatchEventArgs |
IBarcodeTrackingListener | IBarcodeBatchListener |
BarcodeTrackingBasicOverlay | BarcodeBatchBasicOverlay |
BarcodeTrackingBasicOverlayStyle | BarcodeBatchBasicOverlayStyle |
IBarcodeTrackingBasicOverlayListener | IBarcodeBatchBasicOverlayListener |
BarcodeTrackingAdvancedOverlay | BarcodeBatchAdvancedOverlay |
IBarcodeTrackingAdvancedOverlayListener | IBarcodeBatchAdvancedOverlayListener |
BarcodeTracking.RecommendedCameraSettings | BarcodeBatch.RecommendedCameraSettings |
Method names that include BarcodeTracking in the parameter name (e.g. OnObservationStarted(BarcodeTracking barcodeTracking)) become OnObservationStarted(BarcodeBatch barcodeBatch) etc. Update the parameter type and parameter name to match.
Namespace rename
The using directives change with the class names:
| Old (v6) | New (v7) |
|---|---|
using Scandit.DataCapture.Barcode.Tracking.Capture; | using Scandit.DataCapture.Barcode.Batch.Capture; |
using Scandit.DataCapture.Barcode.Tracking.Data; | using Scandit.DataCapture.Barcode.Batch.Data; |
using Scandit.DataCapture.Barcode.Tracking.UI.Overlay; | using Scandit.DataCapture.Barcode.Batch.UI.Overlay; |
Apply a project-wide search-and-replace on Scandit.DataCapture.Barcode.Tracking → Scandit.DataCapture.Barcode.Batch followed by BarcodeTracking → BarcodeBatch. Run the build afterwards and clean up any references the rename missed.
Camera setup — use RecommendedCameraSettings
If the project constructs a CameraSettings by hand (e.g. new CameraSettings { PreferredResolution = ... }), prefer the recommended settings property after the rename:
Before (v6 pattern):
var cameraSettings = new CameraSettings { PreferredResolution = VideoResolution.Auto };
var camera = Camera.GetDefaultCamera();
camera?.ApplySettingsAsync(cameraSettings);After (v7+ pattern):
var camera = Camera.GetDefaultCamera();
camera?.ApplySettingsAsync(BarcodeBatch.RecommendedCameraSettings);If a specific resolution (e.g. VideoResolution.FullHd) is required — and on iOS this is the official sample's choice for better decode range — fetch the recommended settings first and then override only what you need:
var cameraSettings = BarcodeBatch.RecommendedCameraSettings;
cameraSettings.PreferredResolution = VideoResolution.FullHd;
this.camera.ApplySettingsAsync(cameraSettings);BarcodeBatch.RecommendedCameraSettings is a static property, not a method. The Swift form recommendedCameraSettings is a class var — the .NET binding exposes it as a static property here.
Factory rename: nothing to change
BarcodeBatch.Create(context, settings) was the .NET factory in v6 (as BarcodeTracking.Create(context, settings)) and continues to be the factory in v7+. There is no rename from Create(...) to ForDataCaptureContext(...) — the .NET binding has always exposed Create. If the codebase already uses BarcodeTracking.Create(...), the only change is renaming BarcodeTracking → BarcodeBatch (covered above).
Frame disposal stays mandatory
IFrameData.Dispose() (called inside OnSessionUpdated to avoid a frozen / stuttering preview) is required in v6 too — no change. If the v6 code already disposes the frame in a finally block, leave that intact through the rename.
New v7 APIs (optional, mention only if asked)
BarcodeBatchBasicOverlayStyle.Dot— an alternative to the defaultFramestyle. Pass it toBarcodeBatchBasicOverlay.Create(barcodeBatch, dataCaptureView, BarcodeBatchBasicOverlayStyle.Dot).
---
Migration: 7 → 8
The 7→8 step for .NET iOS MatrixScan Batch is mostly mechanical. The factory methods (BarcodeBatch.Create(context, settings) and the overlay Create(...) overloads), the listener / event surface, the session API, and the tracked-barcode model are all unchanged. The one required action is adding explicit SDK initialization at app launch — without it, the app crashes on the first Scandit API call.
Explicit SDK initialization is now required
Scandit 8.0 removed the implicit container bootstrap that 6.x/7.x performed automatically. The app must now call ScanditCaptureCore.Initialize() and ScanditBarcodeCapture.Initialize() before any Scandit type is constructed.
Find the project's AppDelegate (a class registered with [Register("AppDelegate")], typically in AppDelegate.cs) and add the two Initialize() calls at the top of FinishedLaunching (before any window / root view controller creation):
using Scandit.DataCapture.Barcode;
using Scandit.DataCapture.Core;
[Export("application:didFinishLaunchingWithOptions:")]
public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
ScanditCaptureCore.Initialize();
ScanditBarcodeCapture.Initialize();
// ... existing launch code stays below
return true;
}Make sure these using directives are present:
using Scandit.DataCapture.Barcode;
using Scandit.DataCapture.Core;If the project has no AppDelegate (e.g. it's a Mac Catalyst / SceneDelegate-only setup), add the calls to whichever launch hook fires first — but they must run before any DataCaptureContext.ForLicenseKey(...) / BarcodeBatch.Create(...) / DataCaptureView.Create(...) call.
Symptom if this step is skipped: instant launch crash at the first Scandit type construction, because the DI container has no registrations.
BarcodeBatchLicenseInfo introduced in 8.4
If the user wants to inspect which symbologies the active license allows, this is available in 8.4+:
// Hook IDataCaptureContextListener and wait for OnModeAdded before reading the property.
BarcodeBatchLicenseInfo? info = this.barcodeBatch.BarcodeBatchLicenseInfo;
ICollection<Symbology>? licensed = info?.LicensedSymbologies;The property does not exist on dotnet.ios 8.0–8.3 — gate any usage on the installed SDK version.
No other breaking BarcodeBatch changes
BarcodeBatch.Create(context, settings), BarcodeBatchSettings.Create(), IBarcodeBatchListener.OnSessionUpdated(BarcodeBatch, BarcodeBatchSession, IFrameData), the SessionUpdated event, the BarcodeBatchSession properties (AddedTrackedBarcodes, UpdatedTrackedBarcodes, RemovedTrackedBarcodes, TrackedBarcodes), the DataCaptureView.Create(context, frame) iOS overload, and both overlays (BarcodeBatchBasicOverlay.Create(...), BarcodeBatchAdvancedOverlay.Create(...)) are all unchanged in v8 for .NET for iOS.
---
After applying changes
1. Restore NuGet packages (dotnet restore) and rebuild. 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/net/ios/migrate-6-to-7/
- 7 → 8: https://docs.scandit.com/sdks/net/ios/migrate-7-to-8/
3. Show the user a summary of only the changes actually made: which files were edited, which classes were renamed, and anything that required a judgment call (e.g., the using directive renames that the IDE may also auto-fix on save). 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/dotnet.ios/barcode-capture/api.html) to find the correct API before guessing.
Third-Party Multi-Barcode Scanner → MatrixScan Batch Migration (.NET for iOS)
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
usingdirectives and<PackageReference>lines). - Which symbologies are enabled.
- How the project is collecting "all visible barcodes" — an
AVCaptureMetadataOutputdelegate that fires on every frame, a continuous ZXing.Net.Mobile scan loop that restarts itself after each result, a hand-rolledAVCaptureVideoDataOutput+ Vision (VNDetectBarcodesRequest) pipeline, etc. - What result-handling logic exists (deduplication on the string value, accumulation in a
List/HashSet, filtering by symbology / prefix). - What data models are defined.
- How the scanner UI is rendered (full-screen
UIViewControllerwith anAVCaptureVideoPreviewLayer, embedded scanner view, modal presentation).
Common third-party scanners abused for batch use in .NET iOS codebases:
- AVFoundation `AVCaptureMetadataOutput` — Apple's built-in barcode detector. Fires
DidOutputMetadataObjectson the camera queue with an array ofAVMetadataMachineReadableCodeObjectper frame. Already multi-result per frame, but the developer typically rebuilds tracking / dedupe by hand. - AVFoundation + Vision (`VNDetectBarcodesRequest`) — Vision-based detection running on
AVCaptureVideoDataOutputframes. Multi-result; the developer assembles their own tracking layer. - ZXing.Net.Mobile (
ZXing.Mobile.MobileBarcodeScanner,ZXing.BarcodeFormat) — usually a continuous-scan loop that restarts itself after each result. Single-barcode by design; "batch" behavior is emergent from looping. Note: ZXing.Net.Mobile has been unmaintained on iOS for years and does not officially support modernnet*-iostargets — moving to Scandit also lifts the dependency. - ZXing.Net — pure decoder, often paired with a hand-written
AVCaptureVideoDataOutputanalyzer that runsBarcodeReader.Decode(orDecodeMultiple) on each pixel buffer.
MatrixScan Batch replaces all of the above: it owns the camera, runs the recognizer on every frame, tracks each barcode across frames (assigning a stable per-barcode tracking ID), and reports additions / updates / removals via IBarcodeBatchListener.OnSessionUpdated. There is no continuous-scan loop to re-trigger — the session updates fire on their own.
---
Remove
- The third-party
<PackageReference>entries from the.csproj(e.g.ZXing.Net.Mobile,ZXing.Net.Mobile.Forms,ZXing.Net). - All
using ZXing.*;directives. Keepusing AVFoundation;/using Vision;only if other parts of the controller still need them; otherwise remove. - The
AVCaptureSession,AVCaptureDevice,AVCaptureMetadataOutput(and itsIAVCaptureMetadataOutputObjectsDelegatedelegate),AVCaptureVideoDataOutput,AVCaptureVideoPreviewLayer, and any setup code that wires them together. Scandit'sCameraandDataCaptureViewreplace this whole stack. - Vision pipeline pieces:
VNImageRequestHandler,VNDetectBarcodesRequest, the dispatch-queue boilerplate. - The continuous-scan loop / frame analyzer (the
while (...)/_ = StartScanAsync()re-trigger, the metadata-output delegate callback, the per-frame buffer-to-CVPixelBuffer conversion). - Any UI code specific to the old scanner — manually-drawn highlight rectangles, custom viewfinder overlay layers, the
AVCaptureVideoPreviewLayerresize handler. MatrixScan Batch'sDataCaptureView+BarcodeBatchBasicOverlay(orBarcodeBatchAdvancedOverlay) replace all of it.
---
Integrate MatrixScan Batch
Follow references/integration.md. The key shape of the rewrite:
1. Replace the scanner's camera setup with the Scandit camera pipeline. Camera.GetDefaultCamera() → camera.ApplySettingsAsync(BarcodeBatch.RecommendedCameraSettings) → dataCaptureContext.SetFrameSourceAsync(camera). Drive on/off from ViewWillAppear / ViewWillDisappear via camera.SwitchToDesiredStateAsync(FrameSourceState.On / Off). 2. Replace the scanner's preview view with `DataCaptureView`. DataCaptureView.Create(dataCaptureContext, this.View!.Bounds) returns a UIView. Set AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth, then this.View.AddSubview(dataCaptureView) and this.View.SendSubviewToBack(dataCaptureView). Remove the old AVCaptureVideoPreviewLayer / custom preview view from the layout. 3. Replace the scanner's symbology configuration with `BarcodeBatchSettings`. Use the symbology mapping table below. 4. Replace the scanner's per-result callback with `IBarcodeBatchListener.OnSessionUpdated` (or the `SessionUpdated` event). Use the result-pattern mapping table below. Wrap UI work in DispatchQueue.MainQueue.DispatchAsync(...) and always call `frameData.Dispose()` in a `finally` block. 5. Replace any manually-drawn highlight with `BarcodeBatchBasicOverlay`. BarcodeBatchBasicOverlay.Create(barcodeBatch, dataCaptureView) auto-adds itself to the view. Use BarcodeBatchBasicOverlayStyle.Frame (default) or Dot.
When configuring BarcodeBatchSettings, map symbologies from the old scanner using the table below. Do not guess or derive Scandit symbology names from the old library's names — they differ (e.g. AVFoundation's AVMetadataObjectType.QRCode and ZXing's QR_CODE both map to Symbology.Qr, not Symbology.QrCode).
Symbology mapping
AVFoundation AVMetadataObjectType | ZXing.Net / ZXing.Net.Mobile BarcodeFormat | Scandit Symbology.* |
|---|---|---|
QRCode | QR_CODE | Symbology.Qr |
EAN13Code | EAN_13 | Symbology.Ean13Upca |
EAN8Code | EAN_8 | Symbology.Ean8 |
UPCECode | UPC_E | Symbology.Upce |
Code39Code / Code39Mod43Code | CODE_39 | Symbology.Code39 |
Code93Code | CODE_93 | Symbology.Code93 |
Code128Code | CODE_128 | Symbology.Code128 |
ITF14Code / Interleaved2of5Code | ITF | Symbology.InterleavedTwoOfFive |
| (not supported directly) | CODABAR | Symbology.Codabar |
DataMatrixCode | DATA_MATRIX | Symbology.DataMatrix |
AztecCode | AZTEC | Symbology.Aztec |
PDF417Code | PDF_417 | Symbology.Pdf417 |
AVFoundation has no UPC_A constant of its own — UPC-A is reported as EAN13Code with a leading 0. The Scandit equivalent is Symbology.Ean13Upca (which decodes both EAN-13 and UPC-A natively).
If you encounter a symbology not in this table, fetch the BarcodeBatch API reference for the correct Symbology enum value before writing the code.
Result-pattern mapping
| Old scanner concept | MatrixScan Batch equivalent |
|---|---|
| "I got a result, restart the scanner" (ZXing.Net.Mobile loop) | Nothing — OnSessionUpdated fires for every processed frame, and AddedTrackedBarcodes reports the new entries since the last frame. Remove the _ = StartScanAsync() re-trigger. |
result.Text / metadataObject.StringValue | trackedBarcode.Barcode.Data |
result.BarcodeFormat / metadataObject.Type | trackedBarcode.Barcode.Symbology (a Symbology enum value; use new SymbologyDescription(symbology).ReadableName for a string) |
Per-result bounding box (metadataObject.Bounds, VNBarcodeObservation.BoundingBox, result.ResultPoints) | trackedBarcode.Location (Quadrilateral in image-space; the basic overlay draws the highlight for you) |
"Have I seen this code yet?" (manual HashSet<string> dedupe) | trackedBarcode.Identifier is the stable per-barcode tracking ID. New barcodes show up in session.AddedTrackedBarcodes; the same physical code keeps the same identifier across frames until it leaves the view. Track identifiers in your own HashSet<int> if you need a "ever seen" set, or accumulate barcode.Data from AddedTrackedBarcodes. |
| "Which barcodes are currently visible?" | session.TrackedBarcodes — IDictionary<int, TrackedBarcode> keyed by tracking ID. |
AVFoundation per-frame IAVCaptureMetadataOutputObjectsDelegate.DidOutputMetadataObjects(captureOutput, metadataObjects, fromConnection) callback | session.AddedTrackedBarcodes + session.UpdatedTrackedBarcodes + session.RemovedTrackedBarcodes (IDs) on every frame inside OnSessionUpdated. |
Vision VNDetectBarcodesRequest per-frame results array | Same — process per-frame deltas from the session inside OnSessionUpdated. |
---
Preserve
- Custom data models — keep as-is.
- Result accumulation and deduplication logic — move it into
OnSessionUpdated(or theSessionUpdatedevent handler). Iteratesession.AddedTrackedBarcodesand append to the existing collection. Wrap UI updates in `DispatchQueue.MainQueue.DispatchAsync(() => { … })` because `OnSessionUpdated` runs on a background recognition queue. Copy the data you need out of the session before scheduling the UI dispatch — the session is only safe to access from inside the callback. Always end the callback with `frameData.Dispose()` in a `finally` block (or the preview freezes / stutters). - Any downstream business logic triggered on a new barcode (network lookup, database insert).
- Validation / reject behavior — if the old scanner had a "is this code valid?" check, port it as a filter when iterating
AddedTrackedBarcodesinsideOnSessionUpdated.
---
Putting it all together
A typical "AVCaptureMetadataOutput loop replaced with MatrixScan Batch" shape:
using System.Collections.Generic;
using System.Linq;
using CoreFoundation;
using UIKit;
using Scandit.DataCapture.Barcode.Batch.Capture;
using Scandit.DataCapture.Barcode.Batch.Data;
using Scandit.DataCapture.Barcode.Batch.UI.Overlay;
using Scandit.DataCapture.Barcode.Data;
using Scandit.DataCapture.Core.Capture;
using Scandit.DataCapture.Core.Data;
using Scandit.DataCapture.Core.Source;
using Scandit.DataCapture.Core.UI;
public partial class ScannerViewController : UIViewController, IBarcodeBatchListener
{
public const string SCANDIT_LICENSE_KEY = "-- ENTER YOUR SCANDIT LICENSE KEY HERE --";
private DataCaptureContext dataCaptureContext = null!;
private BarcodeBatch barcodeBatch = null!;
private Camera? camera;
private readonly List<ScannedBarcode> scannedBarcodes = new();
private readonly HashSet<int> seenTrackingIds = new();
public ScannerViewController(IntPtr handle) : base(handle) { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.InitializeAndStartBatchScanning();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.barcodeBatch.Enabled = true;
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.On);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.barcodeBatch.Enabled = false;
this.camera?.SwitchToDesiredStateAsync(FrameSourceState.Off);
}
private void InitializeAndStartBatchScanning()
{
this.dataCaptureContext = DataCaptureContext.ForLicenseKey(SCANDIT_LICENSE_KEY);
this.camera = Camera.GetDefaultCamera();
if (this.camera != null)
{
var cameraSettings = BarcodeBatch.RecommendedCameraSettings;
cameraSettings.PreferredResolution = VideoResolution.FullHd;
this.camera.ApplySettingsAsync(cameraSettings);
this.dataCaptureContext.SetFrameSourceAsync(this.camera);
}
BarcodeBatchSettings settings = BarcodeBatchSettings.Create();
settings.EnableSymbologies(new HashSet<Symbology>
{
Symbology.Ean13Upca, // from AVMetadataObjectType.EAN13Code / ZXing EAN_13
Symbology.Code128, // from AVMetadataObjectType.Code128Code / ZXing CODE_128
Symbology.Qr, // from AVMetadataObjectType.QRCode / ZXing QR_CODE
});
this.barcodeBatch = BarcodeBatch.Create(this.dataCaptureContext, settings);
this.barcodeBatch.AddListener(this);
var dataCaptureView = DataCaptureView.Create(this.dataCaptureContext, this.View!.Bounds);
UIView platformView = dataCaptureView;
platformView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight |
UIViewAutoresizing.FlexibleWidth;
this.View.AddSubview(dataCaptureView);
this.View.SendSubviewToBack(dataCaptureView);
BarcodeBatchBasicOverlay.Create(this.barcodeBatch, dataCaptureView);
}
public void OnSessionUpdated(
BarcodeBatch barcodeBatch,
BarcodeBatchSession session,
IFrameData frameData)
{
try
{
// Same dedupe-and-accumulate as the old AVFoundation / ZXing loop,
// but driven by per-frame deltas.
var newScans = session.AddedTrackedBarcodes
.Where(tb => this.seenTrackingIds.Add(tb.Identifier))
.Select(tb => new ScannedBarcode(
tb.Barcode.Data ?? string.Empty,
tb.Barcode.Symbology.ToString()))
.ToList();
if (newScans.Count == 0) return;
DispatchQueue.MainQueue.DispatchAsync(() =>
{
this.scannedBarcodes.AddRange(newScans);
// update UI here
});
}
finally
{
// Mandatory on iOS to avoid a frozen / stuttering preview.
frameData.Dispose();
}
}
public void OnObservationStarted(BarcodeBatch barcodeBatch) { }
public void OnObservationStopped(BarcodeBatch barcodeBatch) { }
}---
When done, show only what changed. Do not list APIs that were unchanged. Include the setup checklist from references/integration.md so the user knows which NuGet packages, Info.plist entries (NSCameraUsageDescription), SupportedOSPlatformVersion, and SDK-8.0+ AppDelegate.FinishedLaunching initialization to add.