
Sparkscan Net Ios
- 24 installs
- 17 repo stars
- Updated August 4, 2026
- scandit/skills
sparkscan-net-ios is a Build-phase integration skill for indie mobile developers shipping .NET-based iOS apps that need industrial-grade barcode scanning.
About
sparkscan-net-ios is a Build-phase integration skill for indie mobile developers shipping .NET-based iOS apps that need industrial-grade barcode scanning. The SKILL.md material is essentially a reference implementation: instantiate DataCaptureContext and SparkScan, embed SparkScanView in a UIKit ViewController, supply a Scandit license key, and implement ISparkScanFeedbackDelegate for success and error feedback. It targets the Scandit Spark capture stack rather than a generic camera preview, so you get consistent scan UX patterns used in retail and logistics apps. Invoke it when you are past prototype and need correct view lifecycle calls—PrepareScanning when appearing and teardown when leaving the screen. You still own App Store entitlements, camera usage strings, and production license provisioning outside the snippet.
- UIKit ViewController pattern with ViewDidLoad setup and PrepareScanning on ViewWillAppear
- SparkScan + SparkScanView + DataCaptureContext initialization with SCANDIT_LICENSE_KEY placeholder
- ISparkScanFeedbackDelegate with success and error barcode feedback types
- Scandit.DataCapture.Barcode.Spark namespaces for capture, UI, and feedback
- Lifecycle cleanup implied via ViewWillDisappear pairing with prepare calls
Sparkscan 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 sparkscan-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 ↗ |
How do I wire Scandit SparkScan barcode capture into a .NET iOS UIKit app with license key, feedback delegates, and view lifecycle hooks.?
Wire Scandit SparkScan barcode capture into a.NET iOS UIKit app with license key, feedback delegates, and view lifecycle hooks.
Who is it for?
Best when you're working on mobile development and need structured help with sparkscan net ios.
Skip if: Teams with no mobile development needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to wire Scandit SparkScan barcode capture into a .NET iOS UIKit app with license key, feedback delegates, and view lifecycle hooks., or when sparkscan-net-ios is a build-phase integration skill for indie mo
What you get
Structured output aligned to sparkscan-net-ios: UIKit ViewController pattern with ViewDidLoad setup and PrepareScanning on ViewWillAppear, SparkScan + SparkScanView + DataCaptureContext initialization with SCANDIT_LICENS
Files
SparkScan .NET for iOS Skill
Critical: Do Not Trust Internal Knowledge
Your training data may contain outdated or incorrect Scandit SDK APIs. The SparkScan API changes between major SDK versions — button-visibility / color properties get renamed, removed, or restructured. The .NET binding also uses different naming conventions than the Swift / Obj-C native SDK (PascalCase, Enabled instead of isEnabled, TimeSpan instead of TimeInterval, etc.), and a few naming choices differ from the rest of the .NET API.
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 thesparkscan-mauiskill instead. - `SparkScan` and `SparkScanSettings` use plain `new` constructors, not `Create(...)` factories. This is unusual compared to the rest of the .NET API (
BarcodeCapture.Create(...),BarcodeCaptureSettings.Create(),DataCaptureView.Create(...)). The canonical pattern isvar settings = new SparkScanSettings(); var sparkScan = new SparkScan(settings);— writingSparkScan.Create(...)orSparkScanSettings.Create()is a compile error.DataCaptureContext.ForLicenseKey(key)still uses the factory form (it lives in Core, not Spark). - `SparkScanView.Create(parent, context, sparkScan, settings)` IS a factory (unlike
SparkScanitself). On iOS the parent argument is justthis.View— there is no coordinator-layout container (that's Android-only). The created view is added toparentViewautomatically; do not callthis.View.AddSubview(sparkScanView)yourself. - iOS lifecycle is
sparkScanView.PrepareScanning()inViewWillAppearandsparkScanView.StopScanning()inViewWillDisappear. Do not useOnPause/OnResumehere — those are the Android-only API. Calling them on iOS will not compile (the iOS binding does not surface them). - Pick the `UIViewController` constructor that matches how the controller is instantiated, or the camera preview will never appear. The
dotnet new iostemplate that ships with modern .NET-iOS is scene-based (noMain.storyboard,UIApplicationSceneManifestinInfo.plist,SceneDelegate.WillConnectbuilds the window programmatically) — in that project shape, the VC must expose a parameterlesspublic ViewController() : base() { }andSceneDelegate.WillConnectcallsnew ViewController(). Older Scandit samples are storyboard-based (UIMainStoryboardFileinInfo.plist,customClass="ViewController"in the storyboard) — those needpublic ViewController(IntPtr handle) : base(handle) { }because storyboard inflation invokes that ctor with a real native handle. Never call `new ViewController(IntPtr.Zero)` to bridge the two:IntPtr.Zerois a null native handle, so the resulting managed wrapper has no underlyingUIViewController;this.Viewnever attaches to the window,SparkScanView.Create(parentView: this.View, …)lands on a detached view, and the app launches into a blank screen with no camera and no scans. Seereferences/integration.md"Scene-based vs storyboard instantiation" for the full callout. NSCameraUsageDescriptioninInfo.plistis mandatory. Without it the app crashes on first camera access. iOS shows the system permission dialog automatically when the camera starts — there is no separate runtime-request API for the camera.- The .NET
ISparkScanListenerhas only two methods:OnBarcodeScanned(SparkScan, SparkScanSession, IFrameData?)andOnSessionUpdated(SparkScan, SparkScanSession, IFrameData?). There are no `OnObservationStarted` / `OnObservationStopped` methods (the wayIBarcodeCaptureListenerhas them). - Prefer the event API (
sparkScan.BarcodeScanned += handler) over the listener interface in idiomatic C# — that's what the official .NET iOS SparkScan sample uses. The event handler receivesSparkScanEventArgswithSession,FrameData, andSparkScan. - `IFrameData` and the image buffers must be `Dispose()`d on iOS. The official sample uses
using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault();andusing var frame = imageBuffer?.ToImage();. Failing to dispose causes a frozen / stuttering preview. TheSparkScanEventArgs.FrameDataitself isIFrameData?. If you do not need the frame, do not retain it. - Symbology names are C# PascalCase:
Symbology.Ean13Upca,Symbology.Ean8,Symbology.Code128,Symbology.InterleavedTwoOfFive,Symbology.Qr,Symbology.DataMatrix. They are not Swift dot-case (.ean13UPCA). - The capture mode's enabled property is
sparkScan.Enabled(notIsEnabled). CodeDuplicateFilterisTimeSpan— notTimeInterval(that is the Swift type). UseTimeSpan.FromMilliseconds(500),TimeSpan.FromSeconds(2.5), orTimeSpan.Zero.SparkScanSettingsdoes not exposeCodeDuplicate.DefaultDuplicateFilter/ReportDataAndSymbologyOnlyOncesentinels — those live onBarcodeCaptureSettings, not on SparkScan. For SparkScan, set theTimeSpandirectly.- Feedback is delivered through
ISparkScanFeedbackDelegate.GetFeedbackForBarcode(Barcode)and assigned withsparkScanView.Feedback = this(or any object that implementsISparkScanFeedbackDelegate). Returningnullfrom the delegate falls back to the default success feedback. - Success feedback:
new SparkScanBarcodeSuccessFeedback()(default), or pass(Color),(Color, Brush), or(Color, Brush, Feedback?). - Error feedback:
new SparkScanBarcodeErrorFeedback(message: "...", resumeCapturingDelay: TimeSpan.FromSeconds(60)). The view shows the error message, the trigger button shows an error state, and scanning resumes after the delay. GetFeedbackForBarcode(Barcode)is invoked on a background thread. Build the feedback object eagerly (inSetupSparkScan/ViewDidLoad) and just return it.SparkScan.BarcodeScanned(andISparkScanListener.OnBarcodeScanned) also run on a background thread. Dispatch any UI update viaDispatchQueue.MainQueue.DispatchAsync(() => { … }).- SDK 8.0+ requires explicit initialization in `AppDelegate.FinishedLaunching`. Call
ScanditCaptureCore.Initialize()+ScanditBarcodeCapture.Initialize()before any Scandit code runs. Without this the SDK's DI container has no registrations and the firstnew SparkScan(...)/SparkScanView.Create(...)call crashes at launch. Not required on 6.x / 7.x. Seereferences/integration.mdfor the canonicalAppDelegate.cstemplate. - 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 (e.g.8.13.0when only8.4.0is published) causesdotnet restoreto fail withUnable to find package Scandit.DataCapture.Core with version (>= …). Seereferences/integration.mdStep 0 for the full procedure. SparkScanView.HardwareTriggerSupportedandSparkScanViewSettings.HardwareTriggerKeyCodeare Android-only and not surfaced on dotnet.ios. Do not reference them in iOS code.SparkScanScanningModeDefaultandSparkScanScanningModeTargetare constructed withnew, both taking(SparkScanScanningBehavior, SparkScanPreviewBehavior). There is no parameterless constructor in the .NET binding; the Swiftinit()overloads (no args) are not surfaced on dotnet.ios.- View state is exposed via
SparkScanView.ViewStateChanged(event ofEventHandler<SparkScanViewStateEventArgs>) — there is noUiListenerproperty on the .NET binding. Use the eventsBarcodeCountButtonTapped,BarcodeFindButtonTapped,LabelCaptureButtonTapped, andViewStateChangedinstead.
Intent Routing
Based on the user's request, load the appropriate reference file before responding:
- Integrating SparkScan from scratch, configuring settings, customizing feedback, customizing the SparkScanView appearance, handling scans, doing async work after a scan, or displaying scanned barcodes in a list (e.g. "add SparkScan to my .NET iOS app", "set up barcode scanning in C#", "how do I use SparkScan in net-ios", "reject barcodes with error feedback", "hide the torch button", "show a custom toast on scan", "use target mode", "crop a thumbnail from the scanned frame", "show scanned barcodes in a list", "add a results table under the scanner", "build a UITableView of scans") → read
references/integration.mdand follow the instructions there (the list-building recipe lives in the "Build a results list (UITableView pattern)" subsection under Optional configuration). - Migrating or upgrading an existing SparkScan integration (e.g. "upgrade from v6 to v7", "migrate my SparkScan", "bump the Scandit .NET SDK to v8", "what changed between SDK versions") → read
references/migration.mdand follow the instructions there. - Replacing a third-party barcode scanner with SparkScan (e.g. "replace my ZXing.Net.Mobile scanner with SparkScan", "migrate from AVFoundation barcode scanning to Scandit", "switch from [library] to SparkScan") → 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) |
| Advanced topics (custom feedback, scanning modes, UI customization, toast messages) | Advanced Configurations |
| Migration between major SDK versions | 6 → 7 · 7 → 8 |
| Full API reference | SparkScan 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/spark-scan*.rst and api/ui/spark-scan-*.rst) are addressed in references/integration.md:
SparkScan—new SparkScan(),new SparkScan(SparkScanSettings),Enabled,ApplySettingsAsync(settings),AddListener(ISparkScanListener)/RemoveListener(ISparkScanListener), eventsBarcodeScanned/SessionUpdated(bothEventHandler<SparkScanEventArgs>),SparkScanLicenseInfo,Dispose.SparkScanSettings—new SparkScanSettings(),new SparkScanSettings(CapturePreset),EnableSymbology,EnableSymbologies(ICollection<Symbology>),EnableSymbologies(CompositeType),GetSymbologySettings,EnabledSymbologies,EnabledCompositeTypes,CodeDuplicateFilter,BatterySaving,ScanIntention,SetProperty/GetProperty<T>/TryGetProperty<T>.SparkScanSession—NewlyRecognizedBarcode,FrameSequenceId,Reset().SparkScanEventArgs—SparkScan,Session,FrameData.ISparkScanListener—OnBarcodeScanned,OnSessionUpdated. (NoOnObservation*callbacks.)SparkScanLicenseInfo—LicensedSymbologies.- Feedback:
SparkScanBarcodeFeedback(abstract),SparkScanBarcodeSuccessFeedback(4 constructors),SparkScanBarcodeErrorFeedback(message, resumeCapturingDelay, …)(4 constructors),ISparkScanFeedbackDelegate.GetFeedbackForBarcode(Barcode). SparkScanView—Create(parentView, context, sparkScan, settings), lifecyclePrepareScanning()/StopScanning(), control methodsStartScanning()/PauseScanning()/ShowToast(string), button visibility properties (BarcodeCountButtonVisible,BarcodeFindButtonVisible,LabelCaptureButtonVisible,TargetModeButtonVisible,ScanningBehaviorButtonVisible,ZoomSwitchControlVisible,PreviewSizeControlVisible,CameraSwitchButtonVisible,TriggerButtonVisible,PreviewCloseControlVisible,TorchControlVisible), color / image customization (ToolbarBackgroundColor,ToolbarIconActiveTintColor,ToolbarIconInactiveTintColor,TriggerButtonCollapsedColor,TriggerButtonExpandedColor,TriggerButtonAnimationColor,TriggerButtonTintColor,TriggerButtonImage),Feedback(theISparkScanFeedbackDelegate), staticDefaultBrush, eventsBarcodeCountButtonTapped,BarcodeFindButtonTapped,LabelCaptureButtonTapped,ViewStateChanged.SparkScanViewSettings—TriggerButtonCollapseTimeout,DefaultScanningMode,DefaultTorchState,SoundEnabled,HapticEnabled,HoldToScanEnabled,HardwareTriggerEnabled,ZoomFactorOut,ZoomFactorIn,ToastSettings,VisualFeedbackEnabled,InactiveStateTimeout,DefaultCameraPosition,DefaultMiniPreviewSize,SmartSelectionCandidateBrush. (NoHardwareTriggerKeyCode— Android-only.)SparkScanToastSettings—ToastEnabled,ToastBackgroundColor,ToastTextColor, plus message strings (TargetModeEnabledMessage,ContinuousModeEnabledMessage,ScanPausedMessage,ZoomedInMessage,TorchEnabledMessage, etc. — see integration.md for the full list).SparkScanViewStateenum (Initial,Idle,Inactive,Active,Error),SparkScanViewEventArgs(View),SparkScanViewStateEventArgs(State).SparkScanMiniPreviewSizeenum (Regular,Expanded).SparkScanPreviewBehaviorenum (Default,Persistent),SparkScanScanningBehaviorenum (Single,Continuous).ISparkScanScanningMode(: IDisposable),SparkScanScanningModeDefault(scanningBehavior, previewBehavior),SparkScanScanningModeTarget(scanningBehavior, previewBehavior).
using UIKit;
namespace MyApp;
public partial class ViewController : UIViewController
{
public ViewController(IntPtr handle) : base(handle) { }
public ViewController() { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.View!.BackgroundColor = UIColor.SystemBackground;
}
}
using CoreFoundation;
using UIKit;
using Scandit.DataCapture.Barcode.Data;
using Scandit.DataCapture.Barcode.Spark.Capture;
using Scandit.DataCapture.Barcode.Spark.Feedback;
using Scandit.DataCapture.Barcode.Spark.UI;
using Scandit.DataCapture.Core.Capture;
namespace MyApp;
public partial class ViewController : UIViewController, ISparkScanFeedbackDelegate
{
public const string SCANDIT_LICENSE_KEY = "-- ENTER YOUR SCANDIT LICENSE KEY HERE --";
private DataCaptureContext dataCaptureContext = null!;
private SparkScan sparkScan = null!;
private SparkScanView sparkScanView = null!;
private SparkScanBarcodeSuccessFeedback successFeedback = null!;
private SparkScanBarcodeErrorFeedback errorFeedback = null!;
public ViewController(IntPtr handle) : base(handle) { }
public ViewController() { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.SetupSparkScan();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.sparkScanView.PrepareScanning();
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.sparkScanView.StopScanning();
}
private void SetupSparkScan()
{
this.dataCaptureContext = DataCaptureContext.ForLicenseKey(SCANDIT_LICENSE_KEY);
SparkScanSettings settings = new();
settings.EnableSymbology(Symbology.Ean13Upca, true);
settings.EnableSymbology(Symbology.Code128, true);
this.sparkScan = new SparkScan(settings);
this.sparkScan.BarcodeScanned += this.BarcodeScanned;
SparkScanViewSettings viewSettings = new();
if (this.View == null)
{
throw new InvalidOperationException("Cannot initialize view");
}
this.sparkScanView = SparkScanView.Create(
parentView: this.View,
context: this.dataCaptureContext,
sparkScan: this.sparkScan,
settings: viewSettings);
this.successFeedback = new SparkScanBarcodeSuccessFeedback();
this.errorFeedback = new SparkScanBarcodeErrorFeedback(
message: "Wrong barcode",
resumeCapturingDelay: TimeSpan.FromSeconds(60));
this.sparkScanView.Feedback = this;
}
private void BarcodeScanned(object? sender, SparkScanEventArgs args)
{
var barcode = args.Session.NewlyRecognizedBarcode;
if (barcode == null) return;
using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault();
using var frame = imageBuffer?.ToImage();
DispatchQueue.MainQueue.DispatchAsync(() => { });
}
SparkScanBarcodeFeedback ISparkScanFeedbackDelegate.GetFeedbackForBarcode(Barcode barcode) =>
IsBarcodeValid(barcode) ? this.successFeedback : this.errorFeedback;
private static bool IsBarcodeValid(Barcode barcode) => barcode.Data != "123456789";
}
using CoreFoundation;
using UIKit;
using Scandit.DataCapture.Barcode.Data;
using Scandit.DataCapture.Barcode.Spark.Capture;
using Scandit.DataCapture.Barcode.Spark.Feedback;
using Scandit.DataCapture.Barcode.Spark.UI;
using Scandit.DataCapture.Core.Capture;
namespace MyApp;
public partial class ViewController : UIViewController, ISparkScanListener
{
public const string SCANDIT_LICENSE_KEY = "-- ENTER YOUR SCANDIT LICENSE KEY HERE --";
private DataCaptureContext dataCaptureContext = null!;
private SparkScan sparkScan = null!;
private SparkScanView sparkScanView = null!;
public ViewController(IntPtr handle) : base(handle) { }
public ViewController() { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.SetupSparkScan();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.sparkScanView.PrepareScanning();
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.sparkScanView.StopScanning();
}
private void SetupSparkScan()
{
this.dataCaptureContext = DataCaptureContext.ForLicenseKey(SCANDIT_LICENSE_KEY);
SparkScanSettings settings = new SparkScanSettings();
settings.EnableSymbology(Symbology.Ean13Upca, true);
settings.EnableSymbology(Symbology.Code128, true);
this.sparkScan = new SparkScan(settings);
this.sparkScan.AddListener(this);
// v6-era feedback: a single SparkScanFeedback POCO on the SparkScan itself.
this.sparkScan.Feedback = new SparkScanFeedback
{
Success = new Feedback(Vibration.DefaultVibration, Sound.DefaultSound),
Error = new Feedback(Vibration.DefaultVibration, Sound.DefaultSound),
};
// v6-era view settings: SoundModeOn / HapticModeOn flags and ContinuousCaptureTimeout.
SparkScanViewSettings viewSettings = new SparkScanViewSettings
{
SoundModeOn = true,
HapticModeOn = true,
ContinuousCaptureTimeout = TimeSpan.FromSeconds(10),
};
this.sparkScanView = SparkScanView.Create(
parentView: this.View!,
context: this.dataCaptureContext,
sparkScan: this.sparkScan,
settings: viewSettings);
// v6-era: per-button visibility named *ButtonVisible (renamed to *ControlVisible in v7).
this.sparkScanView.TorchButtonVisible = true;
}
public void OnBarcodeScanned(SparkScan sparkScan, SparkScanSession session, IFrameData? frameData)
{
var barcode = session.NewlyRecognizedBarcode;
if (barcode == null) return;
DispatchQueue.MainQueue.DispatchAsync(() => { });
}
public void OnSessionUpdated(SparkScan sparkScan, SparkScanSession session, IFrameData? frameData) { }
}
using UIKit;
using ZXing.Mobile;
namespace MyApp;
public record ScannedBarcode(string Value, string Format);
public partial class ScannerViewController : UIViewController
{
private MobileBarcodeScanner scanner = null!;
private readonly List<ScannedBarcode> scannedBarcodes = new();
private UILabel resultLabel = null!;
public ScannerViewController(IntPtr handle) : base(handle) { }
public ScannerViewController() { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.View!.BackgroundColor = UIColor.SystemBackground;
this.resultLabel = new UILabel(this.View.Bounds)
{
TextAlignment = UITextAlignment.Center,
AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight,
};
this.View.AddSubview(this.resultLabel);
this.scanner = new MobileBarcodeScanner();
_ = this.StartScanAsync();
}
private async Task StartScanAsync()
{
var options = new MobileBarcodeScanningOptions
{
PossibleFormats = new List<ZXing.BarcodeFormat>
{
ZXing.BarcodeFormat.EAN_13,
ZXing.BarcodeFormat.CODE_128,
ZXing.BarcodeFormat.QR_CODE
},
UseFrontCameraIfAvailable = false,
};
var result = await this.scanner.Scan(options);
if (result != null && !string.IsNullOrEmpty(result.Text))
{
var barcode = new ScannedBarcode(result.Text, result.BarcodeFormat.ToString());
if (!this.scannedBarcodes.Any(b => b.Value == barcode.Value))
{
this.scannedBarcodes.Add(barcode);
this.resultLabel.Text = $"Last scan: {barcode.Value} ({this.scannedBarcodes.Count} total)";
_ = this.StartScanAsync();
}
}
}
}
{
"skill_name": "sparkscan-net-ios",
"evals": [
{
"id": 1,
"prompt": "I want to add SparkScan to my .NET for iOS app. Here's my empty ViewController: EmptyViewController.cs. I need to scan EAN-13 and Code 128 barcodes for a retail app.",
"expected_output": "The skill reads integration.md, fetches the latest Scandit NuGet versions, writes complete SparkScan integration code into EmptyViewController.cs (new SparkScan(settings), SparkScanView.Create with this.View as parent, ISparkScanFeedbackDelegate, BarcodeScanned event or ISparkScanListener, ViewWillAppear/ViewWillDisappear → PrepareScanning/StopScanning, IFrameData / ImageBuffers using-disposal), and shows the setup checklist (NuGet packages, NSCameraUsageDescription, AppDelegate.FinishedLaunching initialize for SDK 8.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": "A license key placeholder string is present"},
{"text": "DataCaptureContext.ForLicenseKey( is used to create the context"},
{"text": "new SparkScanSettings() is used (not SparkScanSettings.Create(), which does not exist)"},
{"text": "new SparkScan(settings) is used (not SparkScan.Create(...), which does not exist)"},
{"text": "Symbology.Ean13Upca is enabled"},
{"text": "Symbology.Code128 is enabled"},
{"text": "No symbologies other than Ean13Upca and Code128 are enabled"},
{"text": "SparkScanView.Create( is called with this.View (or this.View!) as the parentView argument — NOT a SparkScanCoordinatorLayout (which is Android-only)"},
{"text": "SparkScanView.Create( receives the DataCaptureContext as the context argument"},
{"text": "SparkScanView.Create( receives the SparkScan instance as the sparkScan argument"},
{"text": "SparkScanView.Create( receives a SparkScanViewSettings as the settings argument"},
{"text": "sparkScan.BarcodeScanned += handler is wired (or sparkScan.AddListener if using ISparkScanListener)"},
{"text": "ViewWillAppear forwards to sparkScanView.PrepareScanning() (not sparkScanView.OnResume, which does not exist on iOS)"},
{"text": "ViewWillDisappear forwards to sparkScanView.StopScanning() (not sparkScanView.OnPause)"},
{"text": "OnPause / OnResume are NOT used (those are Android-only)"},
{"text": "No call to BarcodeCapture.Create or barcodeCapture.AddListener appears (SparkScan is not the same API as BarcodeCapture)"},
{"text": "No DataCaptureView is created (SparkScan has its own pre-built UI)"},
{"text": "No BarcodeCaptureOverlay is created"},
{"text": "No Camera.GetDefaultCamera() or dataCaptureContext.SetFrameSourceAsync(...) call is present (SparkScan manages its own camera)"}
]
},
{
"id": 2,
"prompt": "Add SparkScan to my existing .NET iOS ViewController. Here's the ViewController: EmptyViewController.cs. I want to scan QR codes and Data Matrix. When a barcode is scanned, log the barcode data.",
"expected_output": "The skill integrates SparkScan into EmptyViewController.cs with QR and Data Matrix enabled. The BarcodeScanned handler logs the barcode data via Console.WriteLine or NSLog. Shows the setup checklist.",
"files": [
"fixtures/EmptyViewController.cs"
],
"assertions": [
{"text": "Symbology.Qr is enabled"},
{"text": "Symbology.DataMatrix is enabled"},
{"text": "No symbologies other than Qr and DataMatrix are enabled"},
{"text": "new SparkScan( is used (not SparkScan.Create)"},
{"text": "The BarcodeScanned event handler (or OnBarcodeScanned) reads args.Session.NewlyRecognizedBarcode or session.NewlyRecognizedBarcode"},
{"text": "barcode.Data is accessed"},
{"text": "The scan result is logged (Console.WriteLine, System.Diagnostics.Debug.WriteLine, NSLog, or similar)"},
{"text": "ViewWillAppear forwards to sparkScanView.PrepareScanning()"},
{"text": "ViewWillDisappear forwards to sparkScanView.StopScanning()"},
{"text": "Setup checklist mentions Scandit.DataCapture.Barcode and Scandit.DataCapture.Core NuGet packages"},
{"text": "Setup checklist mentions NSCameraUsageDescription"}
]
},
{
"id": 3,
"prompt": "In my .NET iOS SparkScan app, I want to reject barcodes whose value starts with '000' and show an error message in the SparkScan UI for 5 seconds before scanning resumes. Wire this up.",
"expected_output": "The skill implements ISparkScanFeedbackDelegate, builds a SparkScanBarcodeSuccessFeedback and a SparkScanBarcodeErrorFeedback(message, resumeCapturingDelay), assigns the delegate to sparkScanView.Feedback, and returns the error feedback when barcode.Data starts with '000'.",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "The view controller (or a dedicated class) implements ISparkScanFeedbackDelegate"},
{"text": "GetFeedbackForBarcode(Barcode barcode) is implemented"},
{"text": "new SparkScanBarcodeErrorFeedback( is constructed with a message argument and a resumeCapturingDelay argument"},
{"text": "resumeCapturingDelay uses TimeSpan.FromSeconds(5) (or TimeSpan.FromMilliseconds(5000)) — not a bare integer 5, not a double, not a TimeInterval"},
{"text": "new SparkScanBarcodeSuccessFeedback() (or one of its multi-argument constructors) is used for the success case"},
{"text": "sparkScanView.Feedback is assigned to the delegate instance (typically this)"},
{"text": "GetFeedbackForBarcode inspects barcode.Data and returns the error feedback when it starts with '000'"},
{"text": "GetFeedbackForBarcode does NOT call DispatchQueue.MainQueue.DispatchAsync or any UI dispatcher (the delegate runs on a background thread)"},
{"text": "Existing integration code (DataCaptureContext, SparkScan, SparkScanView, PrepareScanning/StopScanning lifecycle) is preserved"}
]
},
{
"id": 4,
"prompt": "Hide the torch and trigger buttons on my .NET iOS SparkScanView and add a custom UIButton that calls StartScanning() programmatically.",
"expected_output": "The skill sets sparkScanView.TorchControlVisible = false and sparkScanView.TriggerButtonVisible = false, then wires a UIButton's TouchUpInside handler to call sparkScanView.StartScanning(). Uses the renamed v7 property TorchControlVisible (not the v6 TorchButtonVisible).",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "sparkScanView.TorchControlVisible = false is set (not TorchButtonVisible, which was removed in v7)"},
{"text": "sparkScanView.TriggerButtonVisible = false is set"},
{"text": "sparkScanView.StartScanning() is called from a UIButton's TouchUpInside event handler (or equivalent control event)"},
{"text": "Existing integration code (DataCaptureContext, SparkScan, SparkScanView, lifecycle) is preserved"}
]
},
{
"id": 5,
"prompt": "I need to filter scans so duplicates of the same code within 500 milliseconds are ignored in my .NET iOS SparkScan app.",
"expected_output": "The skill sets settings.CodeDuplicateFilter = TimeSpan.FromMilliseconds(500) on the SparkScanSettings instance (using TimeSpan, not a bare integer, not a double 0.5, not TimeInterval).",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "settings.CodeDuplicateFilter is set to TimeSpan.FromMilliseconds(500) (not a bare integer 500, not a double 0.5, not TimeInterval)"},
{"text": "The value is applied to a SparkScanSettings instance (not BarcodeCaptureSettings)"},
{"text": "Either CodeDuplicateFilter is set before new SparkScan(settings), or sparkScan.ApplySettingsAsync(settings) is invoked after the change"},
{"text": "Existing integration code (DataCaptureContext, SparkScan, SparkScanView, lifecycle) is preserved"}
]
},
{
"id": 6,
"prompt": "I'm extracting a thumbnail of the scanned barcode by reading args.FrameData?.ImageBuffers in OnBarcodeScanned, but the SparkScan camera preview freezes after the first scan in my .NET iOS app. Help me fix it.",
"expected_output": "The skill identifies that the iOS .NET binding requires explicit disposal of every IFrameData and the ImageBuffer / FrameDataImage. Replaces ad-hoc access with `using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault();` and `using var frame = imageBuffer?.ToImage();` patterns.",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "The skill identifies that IFrameData / ImageBuffer / FrameDataImage must be disposed on iOS"},
{"text": "The skill explains that failing to dispose the frame causes a frozen / non-responsive / stuttering preview"},
{"text": "The fix uses a `using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault();` pattern (or an equivalent explicit Dispose() call)"},
{"text": "If a UIImage / frame image is derived, a `using var frame = imageBuffer?.ToImage();` pattern is used"},
{"text": "Existing integration code (DataCaptureContext, SparkScan, SparkScanView, PrepareScanning/StopScanning lifecycle) is preserved"}
]
},
{
"id": 7,
"prompt": "When a barcode is scanned in my .NET iOS SparkScan app, I want to disable scanning, look up the barcode via an async HTTP call, and re-enable scanning when the lookup completes. Wire this up safely.",
"expected_output": "The skill sets sparkScan.Enabled = false at the start of the BarcodeScanned handler, awaits the async lookup, and re-enables scanning in a finally block. UI updates are dispatched via DispatchQueue.MainQueue.DispatchAsync. The IFrameData using-disposal is preserved.",
"files": [
"fixtures/IntegratedViewController.cs"
],
"assertions": [
{"text": "sparkScan.Enabled = false is set inside the BarcodeScanned handler before the async work"},
{"text": "An async/await call or a Task continuation is used to perform the lookup"},
{"text": "sparkScan.Enabled = true is set after the lookup completes (typically in a finally block)"},
{"text": "DispatchQueue.MainQueue.DispatchAsync is used to dispatch UI work after the async lookup (not RunOnUiThread, which is Android-only)"},
{"text": "Existing integration code (DataCaptureContext, SparkScan, SparkScanView, PrepareScanning/StopScanning lifecycle) is preserved"}
]
}
]
}
{
"skill_name": "sparkscan-net-ios",
"evals": [
{
"id": 1,
"prompt": "I need to upgrade my .NET for iOS SparkScan app from Scandit SDK v6 to v7. Here is my current ViewController: ViewControllerV6.cs.",
"expected_output": "The skill renames v6-era SparkScan APIs: TorchButtonVisible → TorchControlVisible, SoundModeOn → SoundEnabled, HapticModeOn → HapticEnabled. Removes ContinuousCaptureTimeout (deleted in v7). Replaces the v6 SparkScanFeedback POCO assigned to sparkScan.Feedback with the v7 ISparkScanFeedbackDelegate assigned to sparkScanView.Feedback (using SparkScanBarcodeSuccessFeedback / SparkScanBarcodeErrorFeedback). It does NOT rename new SparkScan(settings) — the .NET constructor is unchanged.",
"files": [
"fixtures/ViewControllerV6.cs"
],
"assertions": [
{"text": "sparkScanView.TorchButtonVisible is replaced with sparkScanView.TorchControlVisible"},
{"text": "viewSettings.SoundModeOn is replaced with viewSettings.SoundEnabled (a bool)"},
{"text": "viewSettings.HapticModeOn is replaced with viewSettings.HapticEnabled (a bool)"},
{"text": "viewSettings.ContinuousCaptureTimeout is removed (no replacement; controlled internally)"},
{"text": "sparkScan.Feedback = new SparkScanFeedback { ... } is removed"},
{"text": "The SparkScanFeedback POCO (with Success / Error Feedback properties) is no longer present in the updated code"},
{"text": "A class implementing ISparkScanFeedbackDelegate is introduced (or the view controller itself implements it)"},
{"text": "GetFeedbackForBarcode(Barcode) is implemented and returns SparkScanBarcodeSuccessFeedback / SparkScanBarcodeErrorFeedback"},
{"text": "sparkScanView.Feedback is assigned to the delegate instance (not sparkScan.Feedback)"},
{"text": "new SparkScan(settings) is preserved (not renamed to SparkScan.Create or anything else)"},
{"text": "new SparkScanSettings() is preserved"},
{"text": "ISparkScanListener.OnBarcodeScanned / OnSessionUpdated callbacks (or the BarcodeScanned event subscription) are preserved"},
{"text": "sparkScanView.PrepareScanning() / StopScanning() lifecycle calls are preserved (the iOS-specific methods, not Android's OnPause/OnResume)"},
{"text": "The migration guide URL (https://docs.scandit.com/sdks/net/ios/migrate-6-to-7/) is provided"}
]
},
{
"id": 2,
"prompt": "I'm upgrading my Scandit .NET iOS SparkScan 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 — ScanditCaptureCore.Initialize() and ScanditBarcodeCapture.Initialize() must be called before any Scandit type is constructed. The SparkScan factory pattern (new SparkScan(settings)) is unchanged. Reinforces the canonical IFrameData using-disposal pattern. Provides the v7→v8 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 initialization calls to AppDelegate.FinishedLaunching (or equivalent iOS app-startup hook), not to ViewController.ViewDidLoad"},
{"text": "The skill does NOT instruct the user to replace new SparkScan(settings) with anything else — the .NET SparkScan constructor is unchanged in v7→v8"},
{"text": "The skill does NOT instruct the user to add a sparkScan AddMode/RemoveMode call (that is a cross-platform native SDK change, not .NET iOS)"},
{"text": "The skill reinforces the IFrameData using-disposal pattern (using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault() or equivalent explicit Dispose)"},
{"text": "Migration guide URL (https://docs.scandit.com/sdks/net/ios/migrate-7-to-8/) is provided"}
]
}
]
}
{
"skill_name": "sparkscan-net-ios",
"evals": [
{
"id": 1,
"prompt": "I have a .NET iOS app using ZXing.Net.Mobile for barcode scanning. I want to replace it with SparkScan. Here is my view controller: ZxingViewController.cs",
"expected_output": "Removes all ZXing.Net.Mobile APIs (MobileBarcodeScanner, MobileBarcodeScanningOptions, ZXing.BarcodeFormat). Adds SparkScan integration with matching symbologies (Ean13Upca, Code128, Qr) using new SparkScan(settings) + SparkScanView.Create with this.View as parent. Preserves the ScannedBarcode record and deduplication logic.",
"files": [
"fixtures/ZxingViewController.cs"
],
"assertions": [
{"text": "ZXing.Mobile / ZXing using directives are NOT present in the new code"},
{"text": "MobileBarcodeScanner is NOT instantiated in the new code (may appear in a before/after summary table)"},
{"text": "MobileBarcodeScanningOptions is NOT present in the new code"},
{"text": "Scandit.DataCapture using directives ARE present"},
{"text": "Symbology.Ean13Upca is enabled"},
{"text": "Symbology.Code128 is enabled"},
{"text": "Symbology.Qr is enabled (not Symbology.QrCode, which does not exist)"},
{"text": "Symbology.QrCode does NOT appear as a Symbology enum value (BarcodeFormat.QR_CODE in a summary table is acceptable)"},
{"text": "new SparkScan(settings) is used (not SparkScan.Create or BarcodeCapture.Create)"},
{"text": "SparkScanView.Create( is called with this.View (or this.View!) as the parentView argument (NOT a SparkScanCoordinatorLayout, which is Android-only)"},
{"text": "Either sparkScan.BarcodeScanned += handler is wired, or ISparkScanListener is implemented"},
{"text": "ViewWillAppear forwards to sparkScanView.PrepareScanning()"},
{"text": "ViewWillDisappear forwards to sparkScanView.StopScanning()"},
{"text": "If args.FrameData / ImageBuffers are accessed, they are wrapped in `using var` (or explicitly disposed)"},
{"text": "ScannedBarcode record is preserved in the output"},
{"text": "scannedBarcodes list is preserved in the output"},
{"text": "Deduplication logic (checking for existing value before adding) is preserved"},
{"text": "A summary of changes is shown with what was removed and what was added"},
{"text": "Setup checklist mentions Scandit.DataCapture.Barcode as a NuGet package"},
{"text": "Setup checklist mentions Scandit.DataCapture.Core as a NuGet package"},
{"text": "A concrete version number is used for the dependencies (not a [version] placeholder)"},
{"text": "Setup checklist mentions NSCameraUsageDescription"},
{"text": "Setup checklist mentions calling ScanditCaptureCore.Initialize() and ScanditBarcodeCapture.Initialize() in AppDelegate.FinishedLaunching (required for SDK 8.0+)"}
]
}
]
}
SparkScan .NET for iOS Integration Guide
SparkScan is a pre-built scanning UI for high-volume single-scanning workflows. The SparkScanView overlays a draggable trigger button (and an optional mini preview) on top of any screen, so the user can scan without leaving their current workflow. Unlike BarcodeCapture, you do not wire up a Camera, DataCaptureView, or BarcodeCaptureOverlay yourself — SparkScan owns its own camera and preview.
Examples below use C# 12 and a UIViewController. The same APIs work in storyboards, XIBs, or programmatically-instantiated controllers — adapt ownership of DataCaptureContext, SparkScan, and SparkScanView to the project's existing structure.
Scene-based vs storyboard instantiation — match the constructor to the instantiation path. Thedotnet new iostemplate that ships with modern .NET-iOS is scene-based: noMain.storyboard, noUIMainStoryboardFileinInfo.plist,AppDelegatereturns aUISceneConfigurationfromGetConfiguration, and aSceneDelegate.WillConnectbuilds the window and setsWindow.RootViewControllerprogrammatically. In that case theUIViewControllermust expose a parameterless constructor (public ViewController() : base() { }) andSceneDelegate.WillConnectcallsnew ViewController(). Do not callnew ViewController(IntPtr.Zero)— the(IntPtr handle)constructor is a binding ctor used by storyboard / XIB inflation to wrap an existing native object. Calling it withIntPtr.Zeroproduces a managed wrapper with no nativeUIViewControllerunderneath; the view never attaches to the window,SparkScanView.Create(parentView: this.View, …)ends up on a detached view, and the camera preview never appears. Symptom: the app launches but shows a blank screen, no camera, no scans. If the project is storyboard-based (older Scandit samples follow this pattern —UIMainStoryboardFileinInfo.plist,customClass="ViewController"inMain.storyboard), keep thepublic ViewController(IntPtr handle) : base(handle) { }constructor instead, since storyboard inflation invokes that ctor with a real native handle.
MAUI? Stop. If the project file has<UseMaui>true</UseMaui>, switch to thesparkscan-mauiskill. The MAUI integration uses<scandit:SparkScanView>in XAML and theUseScanditCore/UseScanditBarcode(c => c.AddSparkScanView())builder, which are 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.
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.
- `SupportedOSPlatformVersion` must be at least `15.0` in the
.csproj(matches the MAUI / .NET iOS template default):
<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>- 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).
- SDK initialization (Scandit 8.0+). Initialize the Scandit DI container in
AppDelegate.FinishedLaunchingbefore any Scandit type is constructed. Without this, the firstnew SparkScan(...)/SparkScanView.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; } = null!;
[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.
Integration flow
Ask the user which barcode symbologies they need to scan. When asking, mention that it's important to only enable the symbologies they actually need, as enabling fewer improves scanning performance and accuracy.
Once the user responds, ask which UIViewController they'd like to integrate SparkScan 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. Ensure <SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion> is set in the .csproj. 3. Add NSCameraUsageDescription to Info.plist with a short user-facing description. 4. Add ScanditCaptureCore.Initialize() and ScanditBarcodeCapture.Initialize() to AppDelegate.FinishedLaunching (SDK 8.0+). 5. Replace -- ENTER YOUR SCANDIT LICENSE KEY HERE -- with your key from https://ssl.scandit.com.
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 SparkScanSettings
Choose which barcode symbologies to scan. By default, all symbologies are disabled — enable each one explicitly. Only enable what you need; each extra symbology adds processing time.
SparkScanSettings is constructed with a plain new — there is no `SparkScanSettings.Create()` factory (unlike BarcodeCaptureSettings.Create()).
using Scandit.DataCapture.Barcode.Data;
using Scandit.DataCapture.Barcode.Spark.Capture;
SparkScanSettings settings = new SparkScanSettings();
HashSet<Symbology> symbologies = new()
{
Symbology.Ean13Upca,
Symbology.Ean8,
Symbology.Upce,
Symbology.Code39,
Symbology.Code128,
Symbology.InterleavedTwoOfFive,
};
settings.EnableSymbologies(symbologies);
// Optional: adjust active symbol counts for variable-length 1D symbologies.
settings.GetSymbologySettings(Symbology.Code39).ActiveSymbolCounts =
new short[] { 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };SparkScanSettings members
| Member | Type | Description |
|---|---|---|
new SparkScanSettings() | constructor | All symbologies disabled. |
new SparkScanSettings(CapturePreset) | constructor | Construct from a preset (e.g. for label use cases). |
EnableSymbology(Symbology, bool) | method | Enable/disable a single symbology. |
EnableSymbologies(ICollection<Symbology>) | method | Enable a set in one call. |
EnableSymbologies(CompositeType) | method | Enable symbologies required for the given composite types. |
GetSymbologySettings(Symbology) | method | Returns the per-symbology SymbologySettings (e.g. ActiveSymbolCounts as ICollection<short>). |
EnabledSymbologies | ICollection<Symbology> (get) | Currently enabled symbologies. |
EnabledCompositeTypes | CompositeType (get/set) | Bit-flag of enabled composite types. |
CodeDuplicateFilter | TimeSpan (get/set) | Window to suppress duplicate scans. See the dedicated section below. |
BatterySaving | BatterySavingMode (get/set) | Auto (default), On, Off. |
ScanIntention | ScanIntention (get/set) | Smart (default from 7.0) or Manual. |
SetProperty(string, object) / GetProperty<T>(string) / TryGetProperty<T>(string, out T) | methods | Read/write unstable/experimental engine flags. |
UnlikeBarcodeCaptureSettings,SparkScanSettingsdoes not expose aLocationSelectionproperty — SparkScan controls scan location through its ownSparkScanScanningModeDefault/SparkScanScanningModeTargetmodes (see Step 7).
Step 3 — Create the SparkScan mode
this.sparkScan = new SparkScan(settings);Or, with defaults:
this.sparkScan = new SparkScan();Note: SparkScan is not auto-attached to a DataCaptureContext from the constructor — the context is associated implicitly through SparkScanView.Create(...) in Step 5. Constructing new SparkScan() is enough to start configuring it.
SparkScan members
| Member | Description |
|---|---|
new SparkScan() | Constructor — creates the mode with default settings. |
new SparkScan(SparkScanSettings settings) | Constructor — creates the mode with the provided settings. |
Enabled | bool (get/set) — pause / resume scanning without tearing down the camera. Toggling this from true to false puts the connected SparkScanView into idle mode. |
ApplySettingsAsync(SparkScanSettings) | Task — applies new settings on the next processed frame. |
AddListener(ISparkScanListener) / RemoveListener(ISparkScanListener) | Register or remove a listener. |
event EventHandler<SparkScanEventArgs> BarcodeScanned | Raised on every successful scan. Recommended in idiomatic C#. |
event EventHandler<SparkScanEventArgs> SessionUpdated | Raised on every processed frame (regardless of whether a code was found). |
SparkScanLicenseInfo | SparkScanLicenseInfo? (get) — licensed symbologies (available after IDataCaptureContextListener.OnModeAdded). |
Dispose() | Releases native resources. |
Use either `AddListener` or the events — not both for the same handler. The official .NET iOS SparkScan sample uses the events.
Step 4 — Configure SparkScanViewSettings (optional)
SparkScanViewSettings controls UI behavior of the SparkScan view: hold-to-scan, default torch state, scanning mode, mini preview behavior, toast text, and more. All fields have sensible defaults — only set what you need to change.
using Scandit.DataCapture.Barcode.Spark.UI;
using Scandit.DataCapture.Core.Source;
SparkScanViewSettings viewSettings = new SparkScanViewSettings();
// Examples:
viewSettings.SoundEnabled = false; // Mute success beep
viewSettings.HapticEnabled = true; // Vibrate on success (default true)
viewSettings.TriggerButtonCollapseTimeout = TimeSpan.FromSeconds(-1); // Don't auto-collapse the trigger
viewSettings.InactiveStateTimeout = TimeSpan.FromSeconds(15); // Time before scanning becomes inactive
viewSettings.DefaultMiniPreviewSize = SparkScanMiniPreviewSize.Regular; // or Expanded
viewSettings.DefaultCameraPosition = CameraPosition.WorldFacing; // default; UserFacing for selfie cameraSparkScanViewSettings members
| Member | Type | Description |
|---|---|---|
TriggerButtonCollapseTimeout | TimeSpan | Auto-collapse the trigger button after this delay. Default is 5 s in v7+. Set -1 (or TimeSpan.FromSeconds(-1)) for "never". |
DefaultScanningMode | ISparkScanScanningMode | Either SparkScanScanningModeDefault or SparkScanScanningModeTarget. See Step 7. |
DefaultTorchState | TorchState | Off (default), On, Auto. If set to Auto, the torch control is hidden. |
SoundEnabled | bool | Beep on success. Default true. |
HapticEnabled | bool | Vibrate on success. Default true. |
HoldToScanEnabled | bool | Tap-and-hold vs tap-toggle on the trigger. |
HardwareTriggerEnabled | bool | Available on the .NET iOS binding but has no effect at runtime — hardware triggers are Android-only. Leave at default. |
ZoomFactorOut / ZoomFactorIn | float | Zoom levels for the zoom-switch control. |
ToastSettings | SparkScanToastSettings | Toast appearance and text — see Step 4b. |
VisualFeedbackEnabled | bool | Show the green/red flash on success/error. |
InactiveStateTimeout | TimeSpan | Time to wait before transitioning to Inactive view state. |
DefaultCameraPosition | CameraPosition | WorldFacing (default) or UserFacing. |
DefaultMiniPreviewSize | SparkScanMiniPreviewSize | Regular (default) or Expanded. |
SmartSelectionCandidateBrush | Brush? | Brush used for the smart-selection candidate highlight. |
HardwareTriggerKeyCode is not surfaced on dotnet.ios — it is an Android-only property in the .NET binding. Referencing it in iOS code will not compile.Step 4b — Toast text (SparkScanToastSettings)
The mini preview shows a toast banner for several state transitions; SparkScan provides default English text. To override:
viewSettings.ToastSettings = new SparkScanToastSettings
{
ToastEnabled = true,
ToastBackgroundColor = Color.FromArgb(204, 18, 22, 25),
ToastTextColor = Color.White,
TargetModeEnabledMessage = "Target mode on",
TargetModeDisabledMessage = "Target mode off",
ContinuousModeEnabledMessage = "Continuous mode",
ContinuousModeDisabledMessage = "Single-scan mode",
ScanPausedMessage = "Scanning paused",
ZoomedInMessage = "Zoomed in",
ZoomedOutMessage = "Zoomed out",
TorchEnabledMessage = "Torch on",
TorchDisabledMessage = "Torch off",
UserFacingCameraEnabledMessage = "Front camera",
WorldFacingCameraEnabledMessage = "Back camera",
};Set ToastEnabled = false to suppress all toasts. Individual message strings default to null — when null, the SDK falls back to its built-in text.
Step 5 — Create the SparkScanView
SparkScanView.Create(parentView, context, sparkScan, settings) creates the view and adds it to `parentView` automatically. The parent is just the view controller's View — there is no coordinator-layout container on iOS (that's Android-only).
using Scandit.DataCapture.Barcode.Spark.UI;
using UIKit;
if (this.View == null)
{
throw new InvalidOperationException("Cannot initialize view");
}
this.sparkScanView = SparkScanView.Create(
parentView: this.View,
context: this.dataCaptureContext,
sparkScan: this.sparkScan,
settings: viewSettings);Do not call this.View.AddSubview(this.sparkScanView) — the factory has already done it.
SparkScanView members (iOS)
| Member | Description |
|---|---|
SparkScanView.Create(parentView, context, sparkScan, settings) | Static factory — creates the view and adds it to parentView. |
PrepareScanning() | Call from ViewWillAppear. Required for correct camera lifecycle. |
StopScanning() | Call from ViewWillDisappear. Required — turns the camera off and stops scanning. |
StartScanning() | Programmatically start scanning (no user trigger tap). The view must be in the hierarchy. |
PauseScanning() | Programmatically pause scanning. The view stays attached. |
ShowToast(string) | Show a custom toast in the mini preview. |
Feedback | ISparkScanFeedbackDelegate? — set to an instance of ISparkScanFeedbackDelegate to customize per-scan feedback. |
BarcodeCountButtonVisible / BarcodeFindButtonVisible / LabelCaptureButtonVisible / TargetModeButtonVisible / ScanningBehaviorButtonVisible | bool — toolbar button visibility. All default false. |
ZoomSwitchControlVisible / PreviewSizeControlVisible / CameraSwitchButtonVisible / TriggerButtonVisible / PreviewCloseControlVisible / TorchControlVisible | bool — other UI control visibility. Defaults vary; TriggerButtonVisible defaults to true. |
ToolbarBackgroundColor / ToolbarIconActiveTintColor / ToolbarIconInactiveTintColor | Color? — toolbar color customization. |
TriggerButtonCollapsedColor / TriggerButtonExpandedColor / TriggerButtonAnimationColor / TriggerButtonTintColor | Color? — trigger button color customization. |
TriggerButtonImage | Image? — replace the trigger button icon. |
static DefaultBrush | Brush — the default brush used by the success-feedback overlay. |
event EventHandler<SparkScanViewEventArgs> BarcodeCountButtonTapped | Fires when the Barcode Count toolbar button is tapped. |
event EventHandler<SparkScanViewEventArgs> BarcodeFindButtonTapped | Fires when the Barcode Find toolbar button is tapped. |
event EventHandler<SparkScanViewEventArgs> LabelCaptureButtonTapped | Fires when the Label Capture toolbar button is tapped (dotnet.ios 8.3+). |
event EventHandler<SparkScanViewStateEventArgs> ViewStateChanged | Fires whenever SparkScanViewState transitions (Initial → Idle → Inactive → Active → Error). |
OnPause/OnResumeandHardwareTriggerSupportedare Android-only. They are not surfaced on dotnet.ios — referencing them in an iOS source file will not compile.
Step 6 — Handle scans
The official .NET iOS SparkScan sample uses the event API. The handler receives SparkScanEventArgs with Session (containing NewlyRecognizedBarcode), FrameData, and SparkScan.
using CoreFoundation;
using Scandit.DataCapture.Barcode.Data;
using Scandit.DataCapture.Barcode.Spark.Capture;
// In ViewDidLoad / setup, after creating sparkScan:
this.sparkScan.BarcodeScanned += this.BarcodeScanned;
private void BarcodeScanned(object? sender, SparkScanEventArgs args)
{
var barcode = args.Session.NewlyRecognizedBarcode;
if (barcode == null) return;
// Optional: pull a thumbnail out of the frame data. Always wrap in `using` so the
// image buffer is released — otherwise the preview will freeze.
using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault();
using var frame = imageBuffer?.ToImage();
var location = barcode.GetBarcodeLocation(frame);
var thumbnail = frame?.CropImage(
(int)location.X, (int)location.Y, (int)location.Width, (int)location.Height);
// BarcodeScanned runs on a background thread — dispatch UI work.
DispatchQueue.MainQueue.DispatchAsync(() =>
{
var description = new SymbologyDescription(barcode.Symbology).ReadableName;
// update list / database / UI here
});
}If you only need the barcode itself (no frame data), the body collapses to:
private void BarcodeScanned(object? sender, SparkScanEventArgs args)
{
var barcode = args.Session.NewlyRecognizedBarcode;
if (barcode == null) return;
DispatchQueue.MainQueue.DispatchAsync(() => { /* update UI */ });
}If you prefer the listener interface, implement ISparkScanListener directly on the view controller:
public partial class ViewController : UIViewController, ISparkScanListener
{
public void OnBarcodeScanned(SparkScan sparkScan, SparkScanSession session, IFrameData? frameData)
{
var barcode = session.NewlyRecognizedBarcode;
if (barcode == null) return;
DispatchQueue.MainQueue.DispatchAsync(() => /* update UI */);
// If you accessed frameData, dispose any image buffers you used.
}
public void OnSessionUpdated(SparkScan sparkScan, SparkScanSession session, IFrameData? frameData) { }
}
// In ViewDidLoad:
this.sparkScan.AddListener(this);Heads-up:ISparkScanListenerhas only two methods —OnBarcodeScannedandOnSessionUpdated. There are noOnObservationStarted/OnObservationStoppedcallbacks (unlikeIBarcodeCaptureListener).
SparkScanSession members
| Member | Type | Description |
|---|---|---|
NewlyRecognizedBarcode | Barcode? | The barcode that was just scanned in the most recent frame. null outside OnBarcodeScanned. |
FrameSequenceId | long | Identifier of the current frame sequence (stable until camera interruption). |
Reset() | method | Resets the session state. Only call from inside a listener / event callback. |
SparkScanEventArgs
| Member | Type | Description |
|---|---|---|
SparkScan | SparkScan | The capture mode that raised the event. |
Session | SparkScanSession | The active session. |
FrameData | IFrameData? | The frame that produced the event. May be null. Always wrap any image buffers you read from it in `using`. |
Step 7 — Customize feedback (optional)
Implement ISparkScanFeedbackDelegate to return per-barcode feedback (success or error). Assign it to sparkScanView.Feedback. The delegate is called on a background thread — build the feedback objects once and return them, don't dispatch to the UI thread inside the delegate.
using Scandit.DataCapture.Barcode.Spark.Feedback;
public partial class ViewController : UIViewController, ISparkScanFeedbackDelegate
{
private SparkScanBarcodeSuccessFeedback successFeedback = null!;
private SparkScanBarcodeErrorFeedback errorFeedback = null!;
private void SetupSparkScanFeedback()
{
this.successFeedback = new SparkScanBarcodeSuccessFeedback();
this.errorFeedback = new SparkScanBarcodeErrorFeedback(
message: "Wrong barcode",
resumeCapturingDelay: TimeSpan.FromSeconds(60));
this.sparkScanView.Feedback = this;
}
SparkScanBarcodeFeedback ISparkScanFeedbackDelegate.GetFeedbackForBarcode(Barcode barcode)
{
return IsBarcodeValid(barcode) ? this.successFeedback : this.errorFeedback;
}
private static bool IsBarcodeValid(Barcode barcode) => barcode.Data != "123456789";
}Feedback classes
SparkScanBarcodeFeedback is an abstract base; the two concrete types are:
| Type | Constructors |
|---|---|
SparkScanBarcodeSuccessFeedback | () (default), (Color visualFeedbackColor), (Color visualFeedbackColor, Brush brush), (Color visualFeedbackColor, Brush brush, Feedback? feedback) — read-only properties VisualFeedbackColor, Brush, Feedback. |
SparkScanBarcodeErrorFeedback | (string message, TimeSpan resumeCapturingDelay), (string, TimeSpan, Color), (string, TimeSpan, Color, Brush), (string, TimeSpan, Color, Brush, Feedback?) — read-only properties Message, ResumeCapturingDelay, VisualFeedbackColor, Brush, Feedback. |
Returning null from GetFeedbackForBarcode falls back to the default success feedback. The Feedback parameter is Scandit.DataCapture.Core.Common.Feedback.Feedback (the same type used by BarcodeCaptureFeedback).
Step 8 — Lifecycle management
Forward the view controller's ViewWillAppear / ViewWillDisappear calls into PrepareScanning() / StopScanning(). Both calls are required — without them, the camera and preview won't behave correctly across navigation transitions.
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.sparkScanView.PrepareScanning();
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.sparkScanView.StopScanning();
}SparkScanView handles the camera switching internally — you don't need to call Camera.GetDefaultCamera() / SwitchToDesiredStateAsync(...) yourself, and you do not call OnPause / OnResume (those are Android-only and not surfaced on dotnet.ios).
iOS automatically shows the system camera permission dialog the first time the camera starts. There is no separate runtime-request API to call.
Complete minimal example
using CoreFoundation;
using Foundation;
using UIKit;
using Scandit.DataCapture.Barcode.Data;
using Scandit.DataCapture.Barcode.Spark.Capture;
using Scandit.DataCapture.Barcode.Spark.Feedback;
using Scandit.DataCapture.Barcode.Spark.UI;
using Scandit.DataCapture.Core.Capture;
namespace MyApp;
public partial class ViewController : UIViewController, ISparkScanFeedbackDelegate
{
public const string SCANDIT_LICENSE_KEY = "-- ENTER YOUR SCANDIT LICENSE KEY HERE --";
private DataCaptureContext dataCaptureContext = null!;
private SparkScan sparkScan = null!;
private SparkScanView sparkScanView = null!;
private SparkScanBarcodeSuccessFeedback successFeedback = null!;
private SparkScanBarcodeErrorFeedback errorFeedback = null!;
// Parameterless ctor for scene-based / programmatic instantiation
// (the `dotnet new ios` default). If the project is storyboard-based
// (Info.plist has `UIMainStoryboardFile`), replace this with
// `public ViewController(IntPtr handle) : base(handle) { }` so storyboard
// inflation can pass the native handle. Do not call `new ViewController(IntPtr.Zero)`
// from SceneDelegate — it produces a managed wrapper with no native object
// and the camera preview will never appear.
public ViewController() : base() { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.SetupSparkScan();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.sparkScanView.PrepareScanning();
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.sparkScanView.StopScanning();
}
private void SetupSparkScan()
{
this.dataCaptureContext = DataCaptureContext.ForLicenseKey(SCANDIT_LICENSE_KEY);
SparkScanSettings settings = new();
HashSet<Symbology> symbologies = new()
{
Symbology.Ean13Upca,
Symbology.Ean8,
Symbology.Upce,
Symbology.Code39,
Symbology.Code128,
Symbology.InterleavedTwoOfFive,
};
settings.EnableSymbologies(symbologies);
this.sparkScan = new SparkScan(settings);
this.sparkScan.BarcodeScanned += this.BarcodeScanned;
SparkScanViewSettings viewSettings = new();
if (this.View == null)
{
throw new InvalidOperationException("Cannot initialize view");
}
this.sparkScanView = SparkScanView.Create(
parentView: this.View,
context: this.dataCaptureContext,
sparkScan: this.sparkScan,
settings: viewSettings);
this.SetupSparkScanFeedback();
}
private void SetupSparkScanFeedback()
{
this.successFeedback = new SparkScanBarcodeSuccessFeedback();
this.errorFeedback = new SparkScanBarcodeErrorFeedback(
message: "Wrong barcode",
resumeCapturingDelay: TimeSpan.FromSeconds(60));
this.sparkScanView.Feedback = this;
}
private void BarcodeScanned(object? sender, SparkScanEventArgs args)
{
var barcode = args.Session.NewlyRecognizedBarcode;
if (barcode == null) return;
// Optional thumbnail extraction — always wrap in `using` for proper disposal.
using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault();
using var frame = imageBuffer?.ToImage();
var location = barcode.GetBarcodeLocation(frame);
var thumbnail = frame?.CropImage(
(int)location.X, (int)location.Y, (int)location.Width, (int)location.Height);
DispatchQueue.MainQueue.DispatchAsync(() =>
{
// Update UI on the main thread.
});
}
SparkScanBarcodeFeedback ISparkScanFeedbackDelegate.GetFeedbackForBarcode(Barcode barcode) =>
IsBarcodeValid(barcode) ? this.successFeedback : this.errorFeedback;
private static bool IsBarcodeValid(Barcode barcode) => barcode.Data != "123456789";
}Optional configuration
Build a results list (UITableView pattern)
The minimal Step 6 handler just receives the scan — it doesn't display anything. The official .NET iOS ListBuildingSample displays scans in a UITableView floating beneath the SparkScanView overlay. This subsection is the complete recipe; drop it in if your UI needs to show the scanned barcodes.
No extra NuGet packages are needed — UITableView, UITableViewSource, and UITableViewCell all ship with UIKit.
Z-order matters. Add the table view to this.View before calling SparkScanView.Create(parentView: this.View, …). Subviews added later sit on top, and SparkScanView must float on top of the table to remain interactive (the draggable trigger button, toolbar, and mini preview have to overlay your content, not sit underneath it). The official sample's ViewDidLoad orders calls as SetupHeaderView → SetupTableView → SetupClearView → SetupSparkScan for exactly this reason — SparkScanView is created last.
1. `Models/ListItem.cs` — the row data:
using Scandit.DataCapture.Barcode.Data;
namespace MyApp.Models;
public class ListItem(int number, Symbology symbology, string? data)
{
public int Number { get; } = number;
public string Symbology { get; } = new SymbologyDescription(symbology).ReadableName;
public string Data { get; } = data ?? string.Empty;
}2. `Models/ListItemManager.cs` — thread-safe singleton with a change event:
namespace MyApp.Models;
public class ListItemManager
{
private static readonly Lazy<ListItemManager> instance =
new(() => new ListItemManager(), LazyThreadSafetyMode.PublicationOnly);
public static ListItemManager Instance => instance.Value;
private readonly List<ListItem> items = new();
public event EventHandler? ListsChanged;
public IEnumerable<ListItem> Inventory => this.items;
public int TotalItemsCount => this.items.Count;
public void AddItem(ListItem item)
{
this.items.Add(item);
this.ListsChanged?.Invoke(this, EventArgs.Empty);
}
public void Clear()
{
this.items.Clear();
this.ListsChanged?.Invoke(this, EventArgs.Empty);
}
private ListItemManager() { }
}3. `Views/ItemTableViewCell.cs` — a minimal two-label cell:
using Foundation;
using MyApp.Models;
using UIKit;
namespace MyApp.Views;
public class ItemTableViewCell : UITableViewCell
{
public static readonly NSString Key = new("ItemTableViewCell");
private UILabel title = null!;
private UILabel subtitle = null!;
// `RegisterClassForCellReuse` + `DequeueReusableCell` instantiate cells via this
// `(IntPtr handle)` ctor with a real native handle. This is the same storyboard-inflation
// path described in the "Scene-based vs storyboard instantiation" callout above —
// do NOT call this ctor manually with `IntPtr.Zero`; UIKit's cell pool owns construction.
public ItemTableViewCell(IntPtr handle) : base(handle) { }
public void Configure(ListItem item)
{
if (this.title == null) this.CreateLabels();
this.title.Text = $"Item {item.Number}";
this.subtitle.Text = $"{item.Symbology}: {item.Data}";
}
private void CreateLabels()
{
this.title = new UILabel
{
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.BoldSystemFontOfSize(16),
};
this.subtitle = new UILabel
{
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(14),
TextColor = UIColor.Gray,
};
this.ContentView.AddSubview(this.title);
this.ContentView.AddSubview(this.subtitle);
NSLayoutConstraint.ActivateConstraints(new[]
{
this.title.LeadingAnchor.ConstraintEqualTo(this.ContentView.LayoutMarginsGuide.LeadingAnchor),
this.title.TrailingAnchor.ConstraintEqualTo(this.ContentView.LayoutMarginsGuide.TrailingAnchor),
this.title.TopAnchor.ConstraintEqualTo(this.ContentView.LayoutMarginsGuide.TopAnchor),
this.subtitle.LeadingAnchor.ConstraintEqualTo(this.title.LeadingAnchor),
this.subtitle.TrailingAnchor.ConstraintEqualTo(this.title.TrailingAnchor),
this.subtitle.TopAnchor.ConstraintEqualTo(this.title.BottomAnchor, 4),
this.subtitle.BottomAnchor.ConstraintEqualTo(this.ContentView.LayoutMarginsGuide.BottomAnchor),
});
}
}4. `Views/TableSource.cs` — bridges ListItemManager to the table:
using Foundation;
using MyApp.Models;
using UIKit;
namespace MyApp.Views;
public class TableSource(IEnumerable<ListItem> items) : UITableViewSource
{
public override nint RowsInSection(UITableView tableView, nint section) => items.Count();
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell(ItemTableViewCell.Key, indexPath) as ItemTableViewCell
?? throw new InvalidOperationException("Cannot retrieve cell");
cell.Configure(items.ElementAt(indexPath.Row));
return cell;
}
}Note: the source holds a reference to ListItemManager.Instance.Inventory, which is IEnumerable<ListItem> over the manager's live List<ListItem>. Every ReloadData re-reads the live list — that's why the source itself never needs an Add/Clear API of its own.
5. Wire the table into the view controller. Build it before SparkScanView.Create(...) so the SparkScan overlay lands on top:
private UITableView tableView = null!;
private void SetupTableView()
{
this.tableView = new UITableView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Source = new TableSource(ListItemManager.Instance.Inventory),
RowHeight = 70,
};
this.tableView.RegisterClassForCellReuse(typeof(ItemTableViewCell), ItemTableViewCell.Key);
this.View!.AddSubview(this.tableView);
NSLayoutConstraint.ActivateConstraints(new[]
{
this.tableView.LeadingAnchor.ConstraintEqualTo(this.View.LeadingAnchor),
this.tableView.TrailingAnchor.ConstraintEqualTo(this.View.TrailingAnchor),
this.tableView.TopAnchor.ConstraintEqualTo(this.View.SafeAreaLayoutGuide.TopAnchor),
this.tableView.BottomAnchor.ConstraintEqualTo(this.View.SafeAreaLayoutGuide.BottomAnchor),
});
ListItemManager.Instance.ListsChanged += (_, _) =>
DispatchQueue.MainQueue.DispatchAsync(this.tableView.ReloadData);
}Call SetupTableView from ViewDidLoad before SetupSparkScan:
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.SetupTableView();
this.SetupSparkScan();
}6. Update the Step 6 scan handler to append to the manager. BarcodeScanned runs on a background thread, but SetupTableView already dispatches ReloadData to the main queue via the ListsChanged subscription, so the handler itself just calls AddItem:
private void BarcodeScanned(object? sender, SparkScanEventArgs args)
{
var barcode = args.Session.NewlyRecognizedBarcode;
if (barcode == null) return;
var number = ListItemManager.Instance.TotalItemsCount + 1;
ListItemManager.Instance.AddItem(new ListItem(number, barcode.Symbology, barcode.Data));
}That's the complete pattern — text-only rows, no thumbnails. Want a thumbnail of the scanned barcode in each row? The official ListBuildingSample extracts one from the frame buffer using two helpers it defines locally (the SDK does not ship them):
// Inside BarcodeScanned, after retrieving `barcode`:
using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault();
using var frame = imageBuffer?.ToImage();
var location = barcode.GetBarcodeLocation(frame); // custom extension method
var thumbnail = frame?.CropImage(
(int)location.X, (int)location.Y, (int)location.Width, (int)location.Height); // custom extension method
// then pass `thumbnail` to ListItemBarcode.GetBarcodeLocation(UIImage?) and UIImage.CropImage(int, int, int, int) are extension methods defined in the sample, not SDK APIs — see Extensions/BarcodeExtensions.cs and Extensions/UIImageExtensions.cs in ListBuildingSample and copy them into your project. IFrameData, the image buffer, and the produced UIImage must all be disposed — the using declarations above are mandatory. Failing to dispose causes the preview to stutter or freeze (this is the same disposal rule called out in the iOS gotchas in SKILL.md). To render the thumbnail, add a UIImage? Image property to ListItem, add a UIImageView to ItemTableViewCell and bind it in Configure, and pass the cropped image when calling AddItem(new ListItem(thumbnail, …)).
Target Mode (aim-to-scan)
For precise scanning in crowded environments, change SparkScanViewSettings.DefaultScanningMode from the default SparkScanScanningModeDefault to SparkScanScanningModeTarget:
using Scandit.DataCapture.Barcode.Spark.UI;
viewSettings.DefaultScanningMode = new SparkScanScanningModeTarget(
scanningBehavior: SparkScanScanningBehavior.Single,
previewBehavior: SparkScanPreviewBehavior.Default);SparkScanScanningModeDefault and SparkScanScanningModeTarget are constructed with new. There is no parameterless constructor in the .NET binding — the (SparkScanScanningBehavior, SparkScanPreviewBehavior) constructor is required.
To let users switch modes from the toolbar at runtime:
this.sparkScanView.TargetModeButtonVisible = true;Tracking view state
SparkScanView.ViewStateChanged fires every time the view transitions between Initial, Idle, Inactive, Active, and Error:
this.sparkScanView.ViewStateChanged += (sender, args) =>
{
DispatchQueue.MainQueue.DispatchAsync(() =>
{
switch (args.State)
{
case SparkScanViewState.Active:
this.myButton.SetTitle("STOP SCANNING", UIControlState.Normal);
break;
default:
this.myButton.SetTitle("START SCANNING", UIControlState.Normal);
break;
}
});
};Custom trigger button (hide built-in, control programmatically)
this.sparkScanView.TriggerButtonVisible = false;
// Start scanning from a custom button:
myStartButton.TouchUpInside += (_, _) => this.sparkScanView.StartScanning();
// Pause:
myPauseButton.TouchUpInside += (_, _) => this.sparkScanView.PauseScanning();Showing toolbar buttons
All toolbar buttons default to invisible (except the torch). Enable them through SparkScanView properties, and listen for taps through the corresponding events:
this.sparkScanView.BarcodeCountButtonVisible = true;
this.sparkScanView.BarcodeCountButtonTapped += (s, e) => { /* open Barcode Count screen */ };
this.sparkScanView.BarcodeFindButtonVisible = true;
this.sparkScanView.BarcodeFindButtonTapped += (s, e) => { /* open Barcode Find screen */ };
this.sparkScanView.LabelCaptureButtonVisible = true; // dotnet.ios 8.3+
this.sparkScanView.LabelCaptureButtonTapped += (s, e) => { /* open Label Capture screen */ };
this.sparkScanView.ScanningBehaviorButtonVisible = true; // toggle Single ↔ Continuous from toolbarCustom toast on a scan
this.sparkScanView.ShowToast("Item added");ShowToast is fire-and-forget — the toast lifetime is controlled by the SDK.
CodeDuplicateFilter
Suppress duplicate scans of the same code within a time window. The .NET SparkScan API uses TimeSpan directly — there are no CodeDuplicate.DefaultDuplicateFilter / CodeDuplicate.ReportDataAndSymbologyOnlyOnce sentinels here (those live on BarcodeCaptureSettings).
// Custom 500 ms window
settings.CodeDuplicateFilter = TimeSpan.FromMilliseconds(500);
// Custom 2.5 s window
settings.CodeDuplicateFilter = TimeSpan.FromSeconds(2.5);
// Disable filtering — every detection is reported
settings.CodeDuplicateFilter = TimeSpan.Zero;Set this before constructing the SparkScan. To change at runtime, mutate the settings and call sparkScan.ApplySettingsAsync(settings).
ScanIntention
settings.ScanIntention = ScanIntention.Smart; // default from 7.0
settings.ScanIntention = ScanIntention.Manual; // legacy v6 behaviorBatterySaving
settings.BatterySaving = BatterySavingMode.Auto; // default
settings.BatterySaving = BatterySavingMode.Off;
settings.BatterySaving = BatterySavingMode.On;SparkScanLicenseInfo
Once the SparkScan mode has been associated with a DataCaptureContext and the context emits OnModeAdded, inspect which symbologies the active license allows:
SparkScanLicenseInfo? licenseInfo = this.sparkScan.SparkScanLicenseInfo;
ICollection<Symbology>? licensed = licenseInfo?.LicensedSymbologies;Available from Scandit.DataCapture.Barcode 6.22 onwards on dotnet.ios.
Async work after a scan (Task-based)
When the scan result requires a network or database call, do not block the scanner thread:
private async void BarcodeScanned(object? sender, SparkScanEventArgs args)
{
var data = args.Session.NewlyRecognizedBarcode?.Data;
using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault();
using var frame = imageBuffer?.ToImage();
if (data == null) return;
try
{
var result = await LookupAsync(data);
DispatchQueue.MainQueue.DispatchAsync(() => this.UpdateUi(result));
}
catch (Exception ex)
{
// Log; SparkScan keeps scanning regardless.
}
}async voidis acceptable here because the event handler signature isvoid. Always dispose any frame-data image buffers up front before theawaitso the SDK can recycle them.
Key rules
1. One context per scanning surface — construct DataCaptureContext.ForLicenseKey(key) once and reuse it. 2. `SparkScan` uses `new`, `SparkScanView` uses `Create` — new SparkScan(settings), new SparkScanSettings(), but SparkScanView.Create(parent, context, sparkScan, settings). Don't write SparkScan.Create(...) — it doesn't exist. 3. Parent is `this.View`, no coordinator layout — iOS does not use SparkScanCoordinatorLayout (that's Android-only). The factory adds the view to the parent automatically; do not also call AddSubview. 4. Forward `ViewWillAppear` / `ViewWillDisappear` into `PrepareScanning()` / `StopScanning()` — these are the iOS-specific lifecycle methods on SparkScanView. OnPause / OnResume are Android-only. 5. Event API is idiomatic — prefer sparkScan.BarcodeScanned += handler over AddListener. Both work, but the official sample uses events. 6. Listener has only two callbacks — ISparkScanListener.OnBarcodeScanned and OnSessionUpdated. No OnObservation*. 7. Background thread + main-queue dispatch — BarcodeScanned / OnBarcodeScanned and GetFeedbackForBarcode both run off the UI thread. DispatchQueue.MainQueue.DispatchAsync(() => …) is required for UI updates. 8. Always `using` frame-data image buffers — using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault(); using var frame = imageBuffer?.ToImage();. Failing to dispose causes a frozen / stuttering preview. 9. Feedback delegate, eager construction — build SparkScanBarcodeSuccessFeedback / SparkScanBarcodeErrorFeedback once in SetupSparkScan, return cached instances from GetFeedbackForBarcode. 10. SDK 8.0+ requires `AppDelegate` init — ScanditCaptureCore.Initialize() + ScanditBarcodeCapture.Initialize() at the top of FinishedLaunching. 11. `NSCameraUsageDescription` in `Info.plist` is mandatory. 12. `TimeSpan`, not `TimeInterval` — CodeDuplicateFilter, TriggerButtonCollapseTimeout, InactiveStateTimeout, and SparkScanBarcodeErrorFeedback.resumeCapturingDelay are all TimeSpan. 13. Symbologies are PascalCase — Symbology.Ean13Upca, not .ean13UPCA.
SparkScan .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.22) | 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: SparkScan on dotnet.ios was first published in 6.22. Anything older does not have a SparkScan API on this platform — confirm with the user before assuming a version below 6.22.
---
Step 2: Update the dependency version
Update the SDK version in every <PackageReference>:
Scandit.DataCapture.CoreScandit.DataCapture.Barcode
Restore packages (dotnet restore or rebuild in the IDE) before continuing.
---
Step 3: Apply source-code changes
Search for files that use SparkScan (search for SparkScan, SparkScanSettings, SparkScanView, SparkScanViewSettings, SparkScanEventArgs, ISparkScanListener, ISparkScanFeedbackDelegate) and apply the relevant changes below directly to those files.
---
Migration: 6 → 7
The 6→7 step for .NET iOS SparkScan is primarily about renamed / removed SparkScanView properties and the scan-intention default change. Go through every section below and apply each change that matches the project.
Where these properties live in v6
- On `SparkScanView` — all button-visibility, color, and text properties listed below.
- On `SparkScanViewSettings` —
DefaultHandModeonly (removed in v7).
When searching the project, look for usages on both the view instance and the settings object.
SparkScanView renames
Apply these renames everywhere they appear. Always replace the old name with the new one, preserving the existing value, regardless of what that value is:
Old (v6, on SparkScanView) | New (v7) |
|---|---|
TorchButtonVisible | TorchControlVisible |
CameraButtonBackgroundColor | TriggerButtonCollapsedColor, TriggerButtonExpandedColor, TriggerButtonAnimationColor (see note) |
CaptureButtonTintColor | TriggerButtonTintColor |
FastFindButtonVisible | BarcodeFindButtonVisible |
Note on `CameraButtonBackgroundColor`: v7 splits this into three separate color properties for the collapsed state, expanded state, and animation. If the user set a single color, apply it to all three unless they indicate otherwise.
SparkScanView removed APIs
Remove any usage of these properties — they no longer exist in v7 and will cause compile errors:
CaptureButtonActiveBackgroundColorStopCapturingText,StartCapturingText,ResumeCapturingText,ScanningCapturingText— the trigger button no longer displays textHandModeButtonVisibleSoundModeButtonVisibleHapticModeButtonVisibleShouldShowScanAreaGuides
SparkScanViewSettings removed APIs
DefaultHandMode— removed in v7. Hand-mode is no longer configurable.
TriggerButtonCollapseTimeout default change
The default value changed from "never collapse" to 5 seconds.
- If the project already sets
TriggerButtonCollapseTimeoutexplicitly, leave it as is. - If the project does not set it, do not add it automatically. Instead, inform the user that the button will now collapse after 5 seconds by default and they can set it to
TimeSpan.FromSeconds(-1)to restore the old behavior.
New v7 APIs (optional)
These are available in v7 — mention them only if the user asks:
SparkScanViewState(enumInitial,Idle,Inactive,Active,Error) + theSparkScanView.ViewStateChangedevent of typeEventHandler<SparkScanViewStateEventArgs>.SparkScanViewSettings.DefaultMiniPreviewSize(SparkScanMiniPreviewSize.Regular/Expanded).SparkScanView.PreviewCloseControlVisible,TriggerButtonVisible(defaults totrue),TorchControlVisible.SparkScanView.TriggerButtonImage(Core.Image?) — set a custom trigger icon.SparkScanView.TriggerButtonCollapsedColor/TriggerButtonExpandedColor/TriggerButtonAnimationColor/TriggerButtonTintColor— replacements for v6's color properties.
BarcodeTracking → BarcodeBatch rename
If the project uses BarcodeTracking (MatrixScan) alongside SparkScan, rename all occurrences to BarcodeBatch. The API is otherwise unchanged.
Scan intention default change
The default ScanIntention is now Smart from v7.
- If the project already explicitly sets
ScanIntention.Manual(or any other value) onSparkScanSettings, leave it. - If the project uses a single-image frame source, Smart is incompatible — set
settings.ScanIntention = ScanIntention.Manual. - Otherwise the code still compiles — no change needed. Inform the user that Smart Scan is now the default.
---
Migration: 7 → 8
The 7→8 step for .NET iOS SparkScan is mostly mechanical. Most of the API surface is unchanged. The one required action is adding explicit SDK initialization in AppDelegate.FinishedLaunching — 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 — AppDelegate.FinishedLaunching is the canonical hook.
Open the project's AppDelegate.cs (the class with [Register("AppDelegate")] : UIResponder, IUIApplicationDelegate — or : UIApplicationDelegate in older templates) and add the two calls at the top of FinishedLaunching, before any other launch code:
[Export("application:didFinishLaunchingWithOptions:")]
public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
ScanditCaptureCore.Initialize();
ScanditBarcodeCapture.Initialize();
// ... existing launch code (window creation, root view controller, etc.) stays below
return true;
}Make sure these using directives are present:
using Scandit.DataCapture.Barcode;
using Scandit.DataCapture.Core;If the project uses a SwiftUI-style App shape (no AppDelegate.cs), put the two calls in the equivalent app-startup entry point — they must run before any Scandit type is referenced.
Symptom if this step is skipped: instant launch crash at the first new SparkScan(...) / SparkScanView.Create(...) call, because the DI container has no registrations.
LabelCaptureButtonVisible / LabelCaptureButtonTapped introduced in 8.3
If the user wants a Label Capture toolbar button, these are available in 8.3+:
this.sparkScanView.LabelCaptureButtonVisible = true;
this.sparkScanView.LabelCaptureButtonTapped += (s, e) => { /* navigate to Label Capture screen */ };If the project does not use Label Capture, no action is needed.
TargetModeButtonVisible deprecation in 8.5
The target mode button now toggles SparkScanSettings.SelectionMode between SelectionMode.On and SelectionMode.Off. The property name TargetModeButtonVisible will be renamed in 9.0. Existing code keeps compiling — no required change.
SparkScan text scanning (8.x, opt-in)
v8 adds the ability to scan text alongside barcodes in SparkScan. This is purely additive — existing code is unaffected. Mention it only if the user asks about new features.
Reminder: always dispose frame-data image buffers
This rule is not new in v8, but it is worth re-checking during a migration. The official .NET iOS SparkScan sample uses using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault(); using var frame = imageBuffer?.ToImage();. Without using / Dispose() the preview can freeze or stutter.
No other breaking SparkScan changes
new SparkScan(settings), ISparkScanListener, the BarcodeScanned / SessionUpdated events, SparkScanSession, SparkScanView.Create(...), PrepareScanning() / StopScanning(), and the feedback delegate 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 properties were renamed/removed, and anything that required a judgment call (e.g., how CameraButtonBackgroundColor was split into three properties). Do not list APIs that were already correct or unchanged. 4. If compile errors persist after the changes above, fetch the SparkScan API reference (https://docs.scandit.com/data-capture-sdk/dotnet.ios/barcode-capture/api.html) to find the correct API before guessing.
Third-Party Barcode Scanner → SparkScan 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.
- What result handling logic exists (deduplication, filtering, accumulation).
- What data models are defined.
- How the scanner is launched (modal view controller, embedded view, AVFoundation pipeline).
Common third-party scanners in .NET iOS codebases:
- ZXing.Net.Mobile (
ZXing.Mobile.MobileBarcodeScanner,ZXing.BarcodeFormat) — modal scanner UI, returnsZXing.Result. - ZXing.Net — pure decoder, often paired with AVFoundation for camera frames.
- AVFoundation `AVCaptureMetadataOutput` / `AVMetadataMachineReadableCodeObject` — Apple's built-in barcode scanning via
AVMetadataObject.TypeQRCode,AVMetadataObject.TypeEan13Code, etc. - Third-party wrappers around AVFoundation.
---
Remove
- The third-party
<PackageReference>entries from the.csproj(e.g.ZXing.Net.Mobile,ZXing.Net.Mobile.Forms). - All
using ZXing.*;directives. - The scanner instance, its setup code, the callback / listener conformance.
- Any
AVCaptureSession,AVCaptureMetadataOutput,IAVCaptureMetadataOutputObjectsDelegate/AVCaptureMetadataOutputObjectsDelegateplumbing —SparkScanViewreplaces all of it. - Any UI code specific to the old scanner (modal presentation, preview layer, overlay it provided).
SparkScan replaces the third-party scanner's camera, preview, and UI entirely. There is no separate camera setup or DataCaptureView to wire up — SparkScanView owns its own camera and overlays the trigger button on top of the host view controller's view.
---
Integrate SparkScan
Follow references/integration.md. When configuring SparkScanSettings, 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. ZXing's QR_CODE maps to Symbology.Qr, not Symbology.QrCode).
Symbology mapping
ZXing.Net / ZXing.Net.Mobile BarcodeFormat | AVFoundation AVMetadataObject.Type* | Scandit Symbology.* |
|---|---|---|
QR_CODE | TypeQRCode | Symbology.Qr |
EAN_13 | TypeEAN13Code | Symbology.Ean13Upca |
EAN_8 | TypeEAN8Code | Symbology.Ean8 |
UPC_A | (no direct type; subset of EAN-13) | Symbology.Ean13Upca |
UPC_E | TypeUPCECode | Symbology.Upce |
CODE_39 | TypeCode39Code | Symbology.Code39 |
CODE_93 | TypeCode93Code | Symbology.Code93 |
CODE_128 | TypeCode128Code | Symbology.Code128 |
ITF | TypeITF14Code (note: ITF-14 is a fixed-length subset) | Symbology.InterleavedTwoOfFive |
CODABAR | (no AVFoundation equivalent) | Symbology.Codabar |
DATA_MATRIX | TypeDataMatrixCode | Symbology.DataMatrix |
AZTEC | TypeAztecCode | Symbology.Aztec |
PDF_417 | TypePDF417Code | Symbology.Pdf417 |
If you encounter a symbology not in this table, check the SparkScan API reference for the correct Symbology enum value before writing the code.
---
Preserve
- Custom data models — keep as-is.
- Result accumulation and deduplication logic — move verbatim into the
BarcodeScannedevent handler (orISparkScanListener.OnBarcodeScanned). Wrap UI updates inDispatchQueue.MainQueue.DispatchAsync(() => { … })because the SparkScan callback runs on a background thread. - Always dispose any image buffers you read from
SparkScanEventArgs.FrameData:using var imageBuffer = args.FrameData?.ImageBuffers.LastOrDefault();. - Any downstream business logic triggered on scan result.
- Validation / reject behavior — if the old scanner displayed an error for invalid codes, port that logic into
ISparkScanFeedbackDelegate.GetFeedbackForBarcode(Barcode), returning aSparkScanBarcodeErrorFeedback("...", TimeSpan.FromSeconds(...)).
---
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 key (NSCameraUsageDescription), and AppDelegate initialization (SDK 8.0+) to add.
Related skills
FAQ
What does sparkscan-net-ios do?
sparkscan-net-ios is a Build-phase integration skill for mobile developers shipping.NET-based iOS apps that need industrial-grade barcode scanning.
When should I use sparkscan-net-ios?
When you need to wire Scandit SparkScan barcode capture into a.NET iOS UIKit app with license key, feedback delegates, and view lifecycle hooks., or when sparkscan-net-ios is a build-phase integration skill for mobile developers shipping.
What are the main capabilities?
UIKit ViewController pattern with ViewDidLoad setup and PrepareScanning on ViewWillAppear; SparkScan + SparkScanView + DataCaptureContext initialization with SCANDIT_LICENSE_KEY placeholder; ISparkScanFeedbackDelegate with success and error barcode feedback types.