Skip to footer content
USING IRONBARCODE

MAUI Barcode Scanner with IronBarcode: Step-by-Step Guide

Mobile applications increasingly rely on barcode scanning for inventory management, point-of-sale systems, and product tracking. Building a MAUI barcode scanner allows you to integrate barcode detection directly into your .NET MAUI application, combining a camera feed with image file processing to detect QR codes, Data Matrix, and other barcode formats. While many libraries focus on camera preview, IronBarcode excels at accurately reading barcodes even under challenging conditions -- skewed angles, poor lighting, and damaged labels all handled without extra configuration.

This guide walks through every step of implementing barcode scanning in a .NET MAUI project using IronBarcode. By the end, you will be able to scan multiple barcodes from a single image file, capture barcodes from a device camera, and confidently integrate the library into your own cross-platform projects.

What Are the Prerequisites for Building a MAUI Barcode Scanner?

Before starting, make sure your development environment is in order:

  • Visual Studio 2022 (v17.8 or later) with the .NET MAUI workload installed
  • .NET 10 SDK -- download from the official .NET site
  • Basic C# knowledge -- familiarity with async/await patterns will help
  • Physical device or emulator configured for camera testing
  • IronBarcode license -- a free trial is available for evaluation

Ensuring Visual Studio has the MAUI workload installed before creating your project saves significant troubleshooting time later. You can verify this in Visual Studio Installer under "Individual components" by searching for ".NET Multi-platform App UI development."

Understanding How IronBarcode Fits Into MAUI

.NET MAUI gives you a single codebase that targets Android, iOS, macOS, and Windows. The challenge with barcode scanning in this environment is that each platform handles camera access differently. IronBarcode addresses this by working at the image processing layer -- you capture the image through MAUI's MediaPicker, then hand off the bytes to IronBarcode for analysis.

This separation of concerns keeps your code clean and avoids platform-specific barcode SDKs. IronBarcode's offline processing model also means barcode data never leaves the device, which matters for applications in regulated industries.

Supported Barcode Formats

IronBarcode reads a wide range of formats, including:

Barcode formats supported by IronBarcode
Format Category Formats Common Use Cases
1D Linear Code 128, Code 39, EAN-13, UPC-A, ITF Retail, logistics, healthcare
2D Matrix QR Code, Data Matrix, Aztec, PDF417 Mobile payments, ticketing, manufacturing
Postal USPS, Royal Mail, Deutsche Post Shipping and postal services
Specialty MaxiCode, GS1, MicroPDF417 Supply chain, transport, parcels

How Do You Set Up a MAUI Barcode Scanning Project?

Start by creating a new .NET MAUI App project in Visual Studio 2022. Name it BarcodeScannerApp and select .NET 10 as the target framework. Visual Studio generates the standard MAUI project structure with platform-specific folders for Android, iOS, macOS, and Windows.

Installing IronBarcode via NuGet

Open the NuGet Package Manager Console and run:

Install-Package BarCode

Alternatively, right-click on your project in Solution Explorer, choose "Manage NuGet Packages," search for IronBarCode, and install the latest stable version. For .NET MAUI projects specifically, IronBarcode's NuGet package includes all necessary native dependencies.

Activating Your License

After installation, activate IronBarcode with your license key early in the application lifecycle. The best place is in MauiProgram.cs before the app builder runs:

IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
$vbLabelText   $csharpLabel

Obtain a free trial license key from the IronSoftware website. Trial keys allow you to evaluate all features without time restrictions during development, though output may include a trial watermark until you apply a full license.

How Do You Configure Camera Permissions for Android and iOS?

Platform-specific camera permissions are essential for barcode scanning functionality. Each platform requires specific configuration in its manifest files before MediaPicker.CapturePhotoAsync() will succeed.

Android Permissions

Edit Platforms/Android/AndroidManifest.xml to declare camera access:

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
XML

The android.permission.CAMERA entry requests runtime permission from the user. The uses-feature declarations inform the Google Play Store that your app requires camera hardware and autofocus capability. Without these, Android devices may grant the permission request but still block camera access internally.

For Android 13 and later (API level 33+), you may also need to handle granular media permissions in your MainActivity.cs using ActivityCompat.RequestPermissions. The MAUI MediaPicker abstraction handles most of this automatically, but physical device testing is recommended before release.

iOS Permissions

Modify Platforms/iOS/Info.plist to include the camera usage description:

<key>NSCameraUsageDescription</key>
<string>This app requires camera access to scan barcodes</string>
<key>NSCameraUsageDescription</key>
<string>This app requires camera access to scan barcodes</string>
XML

iOS requires a human-readable explanation for every privacy-sensitive permission. Apple's App Store review process will reject your app if this description is missing or vague. The text appears in the system permission dialog shown to the user the first time the app requests camera access.

For iPadOS, also consider adding NSPhotoLibraryUsageDescription if you plan to let users scan barcodes from saved photos in addition to the live camera.

Windows and macOS

For Windows Desktop and macOS targets, camera access permissions are managed through the application manifest and entitlements files respectively. The MAUI framework handles most of this at the template level, but verify that Package.appxmanifest on Windows includes the webcam device capability.

How Do You Create the Barcode Scanner Interface?

Design a user interface in MainPage.xaml that gives users clear feedback during the scanning process. A minimal but functional layout includes an image preview, a result display area, and a scan trigger button:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="BarcodeScannerApp.MainPage"
             Title="Barcode Scanner">
    <VerticalStackLayout Padding="20" Spacing="20">
        <Label Text="Point the camera at a barcode"
               FontSize="16"
               HorizontalOptions="Center"
               TextColor="#555555" />
        <Image x:Name="CapturedImage"
               HeightRequest="300"
               Aspect="AspectFit"
               BackgroundColor="#F0F0F0" />
        <Label x:Name="ResultLabel"
               Text="Tap Scan to begin"
               FontSize="18"
               HorizontalOptions="Center"
               FontAttributes="Bold" />
        <Label x:Name="FormatLabel"
               Text=""
               FontSize="13"
               HorizontalOptions="Center"
               TextColor="#888888" />
        <Button Text="Scan Barcode"
                Clicked="OnScanClicked"
                BackgroundColor="#007ACC"
                TextColor="White"
                CornerRadius="8"
                HeightRequest="50" />
        <Button Text="Load from Gallery"
                Clicked="OnPickFromGalleryClicked"
                BackgroundColor="#5C5C5C"
                TextColor="White"
                CornerRadius="8"
                HeightRequest="50" />
    </VerticalStackLayout>
</ContentPage>
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="BarcodeScannerApp.MainPage"
             Title="Barcode Scanner">
    <VerticalStackLayout Padding="20" Spacing="20">
        <Label Text="Point the camera at a barcode"
               FontSize="16"
               HorizontalOptions="Center"
               TextColor="#555555" />
        <Image x:Name="CapturedImage"
               HeightRequest="300"
               Aspect="AspectFit"
               BackgroundColor="#F0F0F0" />
        <Label x:Name="ResultLabel"
               Text="Tap Scan to begin"
               FontSize="18"
               HorizontalOptions="Center"
               FontAttributes="Bold" />
        <Label x:Name="FormatLabel"
               Text=""
               FontSize="13"
               HorizontalOptions="Center"
               TextColor="#888888" />
        <Button Text="Scan Barcode"
                Clicked="OnScanClicked"
                BackgroundColor="#007ACC"
                TextColor="White"
                CornerRadius="8"
                HeightRequest="50" />
        <Button Text="Load from Gallery"
                Clicked="OnPickFromGalleryClicked"
                BackgroundColor="#5C5C5C"
                TextColor="White"
                CornerRadius="8"
                HeightRequest="50" />
    </VerticalStackLayout>
</ContentPage>
XML

The layout provides two scan paths: capturing a new photo with the camera and picking an existing image from the gallery. This matters for workflows where users photograph barcodes in advance or receive images via email. The FormatLabel displays the detected barcode format alongside the decoded value, which helps during debugging and user verification.

Adding Scan Status Feedback

For a polished experience, consider wrapping the scan buttons in an ActivityIndicator that shows while processing is underway. IronBarcode's async API makes this straightforward -- you can set IsRunning = true before calling BarcodeReader.ReadAsync and reset it in the finally block.

How Do You Implement the Barcode Reader Functionality?

Implement the core scanning logic in MainPage.xaml.cs. The code below handles both camera capture and gallery selection, with proper async patterns and error handling:

using IronBarCode;
using IronSoftware.Drawing;

namespace BarcodeScannerApp;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private async void OnScanClicked(object sender, EventArgs e)
    {
        await ScanFromSource(() => MediaPicker.Default.CapturePhotoAsync());
    }

    private async void OnPickFromGalleryClicked(object sender, EventArgs e)
    {
        await ScanFromSource(() => MediaPicker.Default.PickPhotoAsync());
    }

    private async Task ScanFromSource(Func<Task<FileResult?>> sourceFunc)
    {
        try
        {
            var photo = await sourceFunc();
            if (photo is null) return;

            using var stream = await photo.OpenReadAsync();
            using var memoryStream = new MemoryStream();
            await stream.CopyToAsync(memoryStream);
            var imageBytes = memoryStream.ToArray();

            // Show the captured image in the UI
            CapturedImage.Source = ImageSource.FromStream(() =>
                new MemoryStream(imageBytes));

            // Process with IronBarcode
            var bitmap = AnyBitmap.FromBytes(imageBytes);
            var options = new BarcodeReaderOptions
            {
                Speed = ReadingSpeed.Balanced,
                ExpectMultipleBarcodes = false
            };

            var results = await BarcodeReader.ReadAsync(bitmap, options);

            if (results.Any())
            {
                var first = results.First();
                ResultLabel.Text = $"Value: {first.Value}";
                FormatLabel.Text = $"Format: {first.BarcodeType}";
            }
            else
            {
                ResultLabel.Text = "No barcode detected";
                FormatLabel.Text = string.Empty;
            }
        }
        catch (FeatureNotSupportedException)
        {
            await DisplayAlert("Unsupported",
                "Camera is not available on this device.", "OK");
        }
        catch (PermissionException)
        {
            await DisplayAlert("Permission Required",
                "Please grant camera permission in Settings.", "OK");
        }
        catch (Exception ex)
        {
            await DisplayAlert("Error",
                $"Scanning failed: {ex.Message}", "OK");
        }
    }
}
using IronBarCode;
using IronSoftware.Drawing;

namespace BarcodeScannerApp;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private async void OnScanClicked(object sender, EventArgs e)
    {
        await ScanFromSource(() => MediaPicker.Default.CapturePhotoAsync());
    }

    private async void OnPickFromGalleryClicked(object sender, EventArgs e)
    {
        await ScanFromSource(() => MediaPicker.Default.PickPhotoAsync());
    }

    private async Task ScanFromSource(Func<Task<FileResult?>> sourceFunc)
    {
        try
        {
            var photo = await sourceFunc();
            if (photo is null) return;

            using var stream = await photo.OpenReadAsync();
            using var memoryStream = new MemoryStream();
            await stream.CopyToAsync(memoryStream);
            var imageBytes = memoryStream.ToArray();

            // Show the captured image in the UI
            CapturedImage.Source = ImageSource.FromStream(() =>
                new MemoryStream(imageBytes));

            // Process with IronBarcode
            var bitmap = AnyBitmap.FromBytes(imageBytes);
            var options = new BarcodeReaderOptions
            {
                Speed = ReadingSpeed.Balanced,
                ExpectMultipleBarcodes = false
            };

            var results = await BarcodeReader.ReadAsync(bitmap, options);

            if (results.Any())
            {
                var first = results.First();
                ResultLabel.Text = $"Value: {first.Value}";
                FormatLabel.Text = $"Format: {first.BarcodeType}";
            }
            else
            {
                ResultLabel.Text = "No barcode detected";
                FormatLabel.Text = string.Empty;
            }
        }
        catch (FeatureNotSupportedException)
        {
            await DisplayAlert("Unsupported",
                "Camera is not available on this device.", "OK");
        }
        catch (PermissionException)
        {
            await DisplayAlert("Permission Required",
                "Please grant camera permission in Settings.", "OK");
        }
        catch (Exception ex)
        {
            await DisplayAlert("Error",
                $"Scanning failed: {ex.Message}", "OK");
        }
    }
}
$vbLabelText   $csharpLabel

This implementation uses a shared ScanFromSource helper method to avoid duplicating the image processing logic between the camera and gallery paths. The AnyBitmap.FromBytes method handles JPEG, PNG, WebP, and other common image formats automatically -- no manual format detection required.

The result object exposes first.Value (the decoded string), first.BarcodeType (the format enum), and first.BarcodeImage (a cropped image of the detected barcode region) among other properties. See the BarcodeResult class documentation for the full list.

Testing Your Scan Implementation

With the code in place, you can test against a standard barcode:

How to Create a MAUI Barcode Scanner Using IronBarcode: Figure 2 - Input test barcode

After scanning, the decoded value appears on screen:

How to Create a MAUI Barcode Scanner Using IronBarcode: Figure 3 - Scanned Barcode Value

How Do You Configure Advanced Scanning Options?

IronBarcode exposes a BarcodeReaderOptions object that lets you fine-tune detection behavior for your specific use case. Understanding these options helps you balance speed against accuracy based on what your application needs.

Targeting Specific Barcode Types

Specifying the exact barcode types you expect dramatically reduces processing time because IronBarcode skips format checks it does not need:

var options = new BarcodeReaderOptions
{
    Speed = ReadingSpeed.Balanced,
    ExpectMultipleBarcodes = true,
    ExpectBarcodeTypes = BarcodeEncoding.QRCode | BarcodeEncoding.Code128
};

var results = await BarcodeReader.ReadAsync(bitmap, options);
var options = new BarcodeReaderOptions
{
    Speed = ReadingSpeed.Balanced,
    ExpectMultipleBarcodes = true,
    ExpectBarcodeTypes = BarcodeEncoding.QRCode | BarcodeEncoding.Code128
};

var results = await BarcodeReader.ReadAsync(bitmap, options);
$vbLabelText   $csharpLabel

How to Create a MAUI Barcode Scanner Using IronBarcode: Figure 4 - Multiple codes scanned from same image

Setting ExpectMultipleBarcodes = true instructs IronBarcode to continue scanning after finding the first result, which is essential for warehouse workflows where a single packing slip may contain a dozen barcodes.

Reading Speed Options

The ReadingSpeed enum offers four levels: ExtremeDetail, Detailed, Balanced, and QuickScan. Use QuickScan for high-volume scenarios where barcodes are clean and well-lit. Switch to Detailed or ExtremeDetail when scanning from low-resolution camera captures or labels that have been partially damaged.

For more about tuning BarcodeReaderOptions, including image correction filters and confidence thresholds, the documentation provides detailed examples.

Image Correction and Pre-processing

IronBarcode includes built-in image correction that automatically handles rotated, skewed, or poorly lit barcodes. You can also apply pre-processing filters manually through BarcodeReaderOptions.ImageFilters:

var options = new BarcodeReaderOptions
{
    Speed = ReadingSpeed.Detailed,
    ImageFilters = new ImageFilterCollection
    {
        new SharpenFilter(),
        new ContrastFilter(1.2f)
    }
};
var options = new BarcodeReaderOptions
{
    Speed = ReadingSpeed.Detailed,
    ImageFilters = new ImageFilterCollection
    {
        new SharpenFilter(),
        new ContrastFilter(1.2f)
    }
};
$vbLabelText   $csharpLabel

Pre-processing filters are particularly useful when the application targets older Android devices with lower-quality camera sensors, or when users are likely to photograph barcodes in suboptimal lighting conditions such as warehouses or outdoor environments.

How Do You Handle Common Troubleshooting Scenarios?

Even with a well-configured MAUI barcode scanner, issues can arise from device-specific behavior, image quality problems, or platform constraints.

Camera Not Opening

If the camera fails to launch, verify that permissions are correctly declared in both AndroidManifest.xml and Info.plist. Then redeploy the app from a clean build rather than a hot reload. On Android, also check whether your test device uses a non-standard camera configuration -- some devices with multiple cameras require explicit lens selection.

On the simulator, MediaPicker.CapturePhotoAsync() is not supported. Always test camera features on a physical device. The emulator does support PickPhotoAsync for gallery selection, which you can use for basic UI testing with pre-loaded images.

Poor Scan Accuracy

If IronBarcode returns no results or incorrect values, try these adjustments:

  • Increase the Speed to ReadingSpeed.Detailed or ExtremeDetail
  • Add SharpenFilter and ContrastFilter to the image filter pipeline
  • Ensure the captured image resolution is at least 720p; lower resolutions cause missed detections with dense formats like Data Matrix
  • Check whether the barcode type is included in your ExpectBarcodeTypes mask

The IronBarcode troubleshooting guide covers additional diagnostic steps for format-specific issues.

Memory Management

Large camera images consume significant memory when loaded into MemoryStream. Always use using statements on all stream objects to ensure disposal. For continuous scanning workflows where users scan multiple items in sequence, also call bitmap.Dispose() explicitly after processing rather than waiting for the garbage collector.

On Android devices with limited heap space, consider downsampling the image before passing it to IronBarcode if scans are of clean, high-contrast barcodes that do not require full resolution to decode accurately.

Platform-Specific iOS Behavior

On iOS, the first time your app requests camera permission, the system shows a one-time dialog. If the user denies it, subsequent calls to CapturePhotoAsync will throw a PermissionException. Handle this case by directing the user to Settings, which you can do with AppInfo.ShowSettingsUI().

Confirm that NSCameraUsageDescription is present in Info.plist before submitting to the App Store. Missing privacy strings cause automatic rejection without a detailed explanation from the review team. Review the Apple Human Interface Guidelines for camera access for best practices on permission request timing and messaging.

What Are Your Next Steps?

Now that you have a working MAUI barcode scanner with IronBarcode, several paths are available depending on your application's requirements:

  • Generate barcodes -- IronBarcode includes a barcode generation API for creating QR codes, Code 128 labels, and other formats from string data
  • Batch scanning -- process multiple image files in a loop using BarcodeReader.ReadAsync with ExpectMultipleBarcodes = true; see the batch scanning examples
  • PDF barcode extraction -- IronBarcode can read barcodes embedded in PDF documents using the same BarcodeReader class; explore the PDF barcode reading documentation
  • Styling and branding -- customize the visual output of generated barcodes with colors, logos, and annotations; see barcode styling options
  • Other IronSoftware products -- if your MAUI app also needs PDF generation, OCR, or spreadsheet support, explore the full Iron Suite for consistent cross-platform capabilities

Start with a free trial license to deploy without restrictions during your evaluation period. For production licensing options and volume pricing, visit the IronBarcode pricing page. The IronBarcode documentation portal and the IronBarcode GitHub repository provide additional code examples and community support.

Frequently Asked Questions

What is the advantage of using IronBarcode for a MAUI barcode scanner?

IronBarcode processes barcodes at the image layer, works offline, supports over 30 formats, and handles challenging conditions like skewed angles and poor lighting without additional configuration.

Can IronBarcode detect multiple barcodes in one image?

Yes. Set ExpectMultipleBarcodes = true in BarcodeReaderOptions and call BarcodeReader.ReadAsync. IronBarcode will return all detected barcodes in the result collection.

How do I configure camera permissions for Android and iOS in MAUI?

For Android, add the CAMERA uses-permission and uses-feature elements to AndroidManifest.xml. For iOS, add NSCameraUsageDescription with a human-readable explanation to Info.plist.

Does IronBarcode support offline barcode scanning?

Yes. IronBarcode processes images entirely on-device without sending data to external servers, which is important for privacy-sensitive applications.

Which barcode formats does IronBarcode support in MAUI?

IronBarcode supports QR Code, Code 128, Code 39, EAN-13, UPC-A, Data Matrix, PDF417, Aztec, MaxiCode, and many more. You can target specific formats via BarcodeEncoding flags.

How do I improve scan accuracy for poor-quality images?

Switch ReadingSpeed to Detailed or ExtremeDetail, add SharpenFilter and ContrastFilter to ImageFilters, and ensure the captured image is at least 720p resolution.

Can IronBarcode read barcodes from PDF files in a MAUI app?

Yes. IronBarcode's BarcodeReader class can extract barcodes embedded in PDF documents using the same ReadAsync API used for image files.

How do I handle PermissionException when camera access is denied?

Catch PermissionException in a try/catch block and call AppInfo.ShowSettingsUI() to direct the user to their device Settings where they can re-enable camera access.

Jordi Bardia
Software Engineer
Jordi is most proficient in Python, C# and C++, when he isn’t leveraging his skills at Iron Software; he’s game programming. Sharing responsibilities for product testing, product development and research, Jordi adds immense value to continual product improvement. The varied experience keeps him challenged and engaged, and he ...
Read More