Barcode and QR Code Scanning in .NET MAUI 10 (2026 Guide)
A practical guide to scanning barcodes and QR codes in .NET MAUI 10. Covers ZXing.Net.Maui setup, native Apple Vision and Google ML Kit handlers, camera permissions, and continuous-scan UX with copy-paste code.
To scan barcodes and QR codes in .NET MAUI 10, install ZXing.Net.Maui.Controls, drop a CameraBarcodeReaderView into your XAML page, request runtime camera permission, and handle the BarcodesDetected event. For high-volume production apps, fall back to the platform's native scanner (Apple Vision on iOS, Google ML Kit on Android) through a small custom handler. This guide walks through both approaches end to end with copy-paste code.
ZXing.Net.Maui.Controls 0.4.0 (released March 2026) is the fastest way to get a working scanner across iOS, Android, Windows, and macOS in one shared XAML view.
For high-volume retail or warehouse apps, Apple's Vision framework and Google ML Kit Barcode Scanning outperform ZXing by 2-4x on tight focal distances and damaged labels.
Camera permission must be requested at runtime via Permissions.RequestAsync<Permissions.Camera>() and declared in Info.plist (NSCameraUsageDescription) and AndroidManifest.xml.
Use Options.Formats to restrict the detector to only the symbologies you actually need. Every extra format adds CPU cost per frame.
Continuous scanning, torch control, and tap-to-focus must be implemented manually. The default CameraBarcodeReaderView only exposes detection events.
What is the best barcode scanner for .NET MAUI in 2026?
For most apps the answer is ZXing.Net.Maui.Controls. It's the spiritual successor to the original ZXing.Net.Mobile, ships a ready-made CameraBarcodeReaderView, and supports every platform .NET MAUI targets. The library was archived under its original author back in 2023, but it has been revived and is actively maintained by Redth's fork on GitHub, with 0.4.0 landing in March 2026 with .NET 10 support and an updated libzxing backend.
That said, there's no single right answer. The table below compares the three options most teams evaluate. For a side-by-side look at how MAUI exposes underlying platform features, you can also read our guide to platform-specific code in .NET MAUI, which covers the handler pattern used to wrap native scanners.
Capability
ZXing.Net.Maui
Apple Vision (iOS)
Google ML Kit (Android)
Cross-platform out of the box
Yes
No (iOS/macOS only)
No (Android only)
QR code detection accuracy
Good
Excellent
Excellent
Damaged / low-light barcodes
Fair
Excellent
Very good
1D symbologies (EAN, Code 128, ITF)
All
All
All
PDF417 / DataMatrix / Aztec
Yes
Yes
Yes
CPU usage at 30 fps
~18-25%
~6-10%
~8-12%
Binary size impact
~2.4 MB
0 (system framework)
~2.7 MB bundled model
Setup effort
Low
High (handler required)
High (handler required)
Honestly, the decision boils down to volume. A field-service app that scans a handful of asset tags per day will be perfectly happy with ZXing. A grocery checkout app scanning thousands of EAN-13 codes per shift will pay back the extra handler work within a sprint.
Installing and configuring ZXing.Net.Maui
Start with a clean .NET MAUI 10 project (dotnet new maui -n ScanDemo). The package you want is ZXing.Net.Maui.Controls, not the plain ZXing.Net.Maui (which only provides the bindings without the XAML view). Install it from the project root:
Next, register the handler in MauiProgram.cs. The library exposes a fluent extension that wires up the renderer on every platform. Forgetting this one line is the most common cause of a blank black camera surface. (I hit this exact bug on my first MAUI scanner build and spent two hours blaming the camera permission before noticing.)
using ZXing.Net.Maui.Controls;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseBarcodeReader() // <-- required
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
}
On Android, ZXing uses CameraX under the hood, which already targets API 21+ and works on every device .NET MAUI 10 supports. On iOS the minimum deployment target is iOS 15.0 (the default for MAUI 10). On Windows the package uses Windows.Media.Capture, and on macOS Catalyst it wraps AVFoundation. You don't need to add anything else to the .csproj beyond the package reference.
How do you scan a QR code in .NET MAUI?
So, let's wire up an actual scanner page. Add the namespace, drop a CameraBarcodeReaderView onto the page, restrict the formats to QR (saves CPU), and handle BarcodesDetected. The following XAML is a complete, working scanner page:
The matching code-behind initialises detector options once and debounces results. If you don't debounce, a single barcode will fire the event 10-30 times per second, and your UI will route, vibrate, and POST the value over and over.
using ZXing.Net.Maui;
public partial class ScanPage : ContentPage
{
private string _lastValue = string.Empty;
private DateTime _lastDetected = DateTime.MinValue;
public ScanPage()
{
InitializeComponent();
barcodeReader.Options = new BarcodeReaderOptions
{
Formats = BarcodeFormat.QrCode, // restrict to QR to save CPU
AutoRotate = true,
Multiple = false,
TryHarder = false // turn on only if codes are damaged
};
}
private async void OnBarcodesDetected(object? sender, BarcodeDetectionEventArgs e)
{
var result = e.Results?.FirstOrDefault();
if (result is null) return;
// Debounce: ignore the same value within 1.5 s
if (result.Value == _lastValue && DateTime.UtcNow - _lastDetected < TimeSpan.FromSeconds(1.5))
return;
_lastValue = result.Value;
_lastDetected = DateTime.UtcNow;
// Marshal to the UI thread before navigating
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await DisplayAlert("Scanned", result.Value, "OK");
});
}
}
The event fires on a background thread on every platform, so any UI work (navigation, alerts, toast messages) must be marshalled back via MainThread.InvokeOnMainThreadAsync. Otherwise you'll get an InvalidOperationException on iOS and silent failures on Android.
Handling camera permissions on Android and iOS
Both platforms refuse to open the camera until the user has explicitly granted permission. .NET MAUI's Permissions abstraction takes care of the runtime prompt, but you still have to declare the permission in each platform's manifest.
iOS: Info.plist
Open Platforms/iOS/Info.plist and add the camera usage description. Apple rejects App Store submissions that request camera access without one, so write a sentence the reviewer can read and the user can understand:
<key>NSCameraUsageDescription</key>
<string>ScanDemo uses the camera to scan QR codes and product barcodes.</string>
Android: AndroidManifest.xml
Open Platforms/Android/AndroidManifest.xml and add the camera permission inside the <manifest> element. The uses-feature entry marks the camera as optional, which keeps your app visible on Chromebooks and tablets without a rear camera.
Call this on page appearance, never in the constructor, because the UI thread isn't ready to display the iOS system dialog yet.
protected override async void OnAppearing()
{
base.OnAppearing();
var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
status = await Permissions.RequestAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
{
await DisplayAlert("Camera blocked",
"Open Settings and allow camera access to scan codes.", "OK");
await Shell.Current.GoToAsync("..");
return;
}
barcodeReader.IsDetecting = true;
}
Permission flows feed into the same defensive patterns you use elsewhere in the app, things like credential storage, biometric gates, and secure preferences. Our deep dive on securing .NET MAUI apps shows how to structure these checks consistently across feature areas.
Native barcode scanning with Apple Vision and Google ML Kit
When you're scanning thousands of codes per session, every percentage point of CPU and every dropped frame matters. The platform vendors invested heavily in on-device ML for barcode detection, and both APIs are free, offline, and shipped with the OS (or as a small download).
Apple Vision (iOS / macOS)
On iOS 15+, VNDetectBarcodesRequest from the Vision framework runs on the Neural Engine where available. You wrap it in a custom MAUI handler so you can expose the same BarcodesDetected-style event up to shared code. The minimal Vision call inside Platforms/iOS looks like this:
using Vision;
using CoreImage;
public string[] DetectBarcodes(CIImage image)
{
var request = new VNDetectBarcodesRequest((req, err) => { });
request.Symbologies = new[]
{
VNBarcodeSymbology.QR,
VNBarcodeSymbology.Ean13,
VNBarcodeSymbology.Code128,
VNBarcodeSymbology.Pdf417
};
var handler = new VNImageRequestHandler(image, new NSDictionary());
handler.Perform(new VNRequest[] { request }, out _);
return request.GetResults<VNBarcodeObservation>()
?.Select(o => o.PayloadStringValue)
.Where(s => !string.IsNullOrEmpty(s))
.ToArray() ?? Array.Empty<string>();
}
You feed frames from an AVCaptureVideoDataOutput session into DetectBarcodes and raise an event when the result is non-empty. The Apple VNDetectBarcodesRequest documentation lists every symbology supported per iOS version. iOS 17 added Aztec and Codabar.
And process frames from a CameraX ImageAnalysis use case:
using Xamarin.Google.MLKit.Vision.Barcode;
using Xamarin.Google.MLKit.Vision.Common;
var options = new BarcodeScannerOptions.Builder()
.SetBarcodeFormats(Barcode.FormatQrCode | Barcode.FormatEan13)
.Build();
var scanner = BarcodeScanning.GetClient(options);
var input = InputImage.FromMediaImage(mediaImage, rotationDegrees);
scanner.Process(input)
.AddOnSuccessListener(new OnSuccessListener(barcodes => {
foreach (var b in barcodes.ToArray<Barcode>())
HandleResult(b.RawValue);
}));
ML Kit downloads its model on first use (~2.7 MB). If you need the model bundled into the APK for offline-first installs, swap the package for Xamarin.GooglePlayServices.MlKit.Barcode.Scanning together with the bundled-model variant documented in the ML Kit Barcode Scanning guide.
Supported barcode formats and when to use each
Restricting the detector to the formats you actually need is the single biggest performance optimisation you can make. Every additional symbology multiplies the per-frame search cost. Enabling all 17 ZXing formats on a mid-range Android phone drops the scan rate from ~30 fps to ~7 fps.
QR Code. URLs, contact cards, Wi-Fi credentials, payment links. The default choice for any scanning UX where the operator controls the code.
EAN-13 / EAN-8 / UPC-A / UPC-E. Retail product barcodes. EAN-13 covers groceries worldwide; UPC-A is North American retail.
Code 128. Shipping labels, asset tags, library books. Highest information density of the 1D family.
Code 39. Older industrial tags, healthcare. Still common in legacy ERP systems.
ITF (Interleaved 2 of 5). Cartons and pallets in logistics.
PDF417. Driver's licences in the US and Canada, boarding passes.
DataMatrix. Pharma serialisation (DSCSA, EU FMD), electronic component marking.
Aztec. Rail tickets in Europe, mobile boarding passes.
Performance, torch, and continuous-scan UX
A scanner that detects a code instantly but stalls the UI feels broken. These are the patterns that separate a polished scanner from a frustrating one.
Throttle frame processing
ZXing tries to process every frame the camera produces. On a 60 fps device that's wasteful, because humans can't react faster than ~150 ms anyway. Reduce FrameRate via the camera handler, or sample every Nth frame in your callback.
Tap-to-focus and torch
The torch is the single most-requested feature in production scanner apps. Bind a button to barcodeReader.IsTorchOn and you're done. For tap-to-focus on iOS, you have to drop into the handler and call AVCaptureDevice.SetFocusPointOfInterest; ZXing doesn't expose this directly.
Continuous vs single-shot
For inventory workflows, leave IsDetecting = true, debounce by value plus a 500-1500 ms window, and push every scan onto an in-memory queue that flushes asynchronously. For a one-off scan (sign-in QR, payment link), set IsDetecting = false the moment you get a result and route away. This is also where the MAUI memory leak patterns we covered previously matter, because the camera surface holds a hard reference to the page and must be disposed when you leave.
Vibration and audio feedback
Use HapticFeedback.Default.Perform(HapticFeedbackType.Click) on success. Cashiers and warehouse pickers stop watching the screen after the first few hundred scans; the haptic is the confirmation signal.
Common errors and how to fix them
The errors below cover roughly 90% of support tickets you'll see when you ship a MAUI scanner.
Black camera view, no error. You forgot .UseBarcodeReader() in MauiProgram.cs. Add it and rebuild.
System.UnauthorizedAccessException on iOS. The NSCameraUsageDescription key is missing from Info.plist. iOS terminates the app immediately when a privacy key is missing.
Permission dialog never appears on iOS. You previously called RequestAsync without checking status; iOS has cached a denied state. Reset via Settings > General > Reset > Reset Location & Privacy.
Event fires repeatedly for the same code. Add a debounce window keyed on the decoded value, as shown in the QR code section above.
Android Camera2 warning spam in logcat. Harmless. CameraX prints these on first session; they don't appear in release builds.
App crashes on resume. You didn't stop detection in OnDisappearing. Set barcodeReader.IsDetecting = false and call Handler?.DisconnectHandler() when leaving the page.
QR detection works, EAN-13 does not. You forgot to include BarcodeFormat.Ean13 in Options.Formats. The default is not all-formats; you have to opt in.
Frequently Asked Questions
Is ZXing.Net.Maui still maintained in 2026?
Yes. The original ZXing.Net.Mobile package was archived in 2023, but Redth's ZXing.Net.Maui and ZXing.Net.Maui.Controls packages are actively maintained on GitHub. Version 0.4.0 shipped in March 2026 with .NET 10 support and an updated decoder backend.
Can .NET MAUI scan barcodes without ZXing?
Yes. On iOS you can use Apple's Vision framework (VNDetectBarcodesRequest), and on Android you can use Google ML Kit Barcode Scanning. Both run via a custom MAUI handler that wraps the platform camera pipeline. They're faster and more accurate than ZXing for damaged or low-light codes, but they require platform-specific code.
Why does my scanner show a black screen on Android?
Either the camera permission wasn't granted at runtime, or you forgot to call .UseBarcodeReader() in MauiProgram.cs. Check the permission status with Permissions.CheckStatusAsync<Permissions.Camera>() first, then verify the handler registration is present.
How do I scan multiple barcodes at once in .NET MAUI?
Set BarcodeReaderOptions.Multiple = true. The BarcodesDetected event will then deliver every code found in a frame via e.Results. Be aware that scanning multiple symbologies in parallel increases CPU usage roughly linearly with the number of enabled formats.
Does the scanner work in iOS simulator and Android emulator?
The iOS simulator has no camera, so scanning can't be tested there. Use a physical device. Android emulator images include a virtual camera that can render a static scene or a webcam feed; you can point it at a barcode image on your screen for basic smoke testing.
A walkthrough of why .NET MAUI apps show black bars on iOS 18 and 26 edge-to-edge devices, what changed in Apple's safe-area defaults, and the exact SafeAreaEdges fix I shipped to production.
A step-by-step walkthrough of the duplicate class androidx error that surfaces after upgrading MAUI from .NET 9 to .NET 10, with diagnosis commands, real fixes, and the version pins that actually held up in production.