How to extract QR codes and barcodes from documents

Available on: iOS Android

Note: This guide covers extracting barcodes from documents. For real-time, continuous barcode scanning (e.g., warehouse scanning, inventory management), see the Barcode Scanning Quick Start guide.

As part of its structured data capabilities, the Genius Scan SDK is able to detect barcodes on documents. This enables you to get both a clean image of the document as well as structured data representing the barcodes present on the document. For instance, this may let you send a scanned document to the proper backend depending on a barcode present on the document.

We recommend using the Genius Scan SDK if you are building a barcode or QR code scanning feature as part of a document acquisition process.

The following readable code formats are supported:

  • 1D codes: Code 128, Code 39, Code 93, EAN-13, EAN-8, UPC-A, UPC-E, ITF, Codabar (iOS 15+), MSI Plessey (iOS 17+)
  • 2D codes: QR Code, Data Matrix, PDF417, Aztec, Micro PDF417 (iOS 15+), Micro QR (iOS 15+)
  • GS1 Formats: GS1 DataBar (iOS 15+)

In this guide, we will write a very simple barcode scanning app. Writing this app should take about 10 minutes.

Getting started

Add the Genius Scan SDK as a dependency to your project by following the platform-specific guide:

Implement the barcode scanning scan flow

You will instantiate a scan flow with a specific configuration:

  • Set structured data to barcode detection
  • Specify which code types to detect (for better performance)
  • Set multi-page to false (single document)
  • Skip post-processing if review isn’t needed
let configuration = GSKScanFlowConfiguration()
configuration.structuredData = [.barcode]
configuration.structuredDataBarcodeTypes = [.ean13]
configuration.multiPage = false
configuration.skipPostProcessingScreen = true
val configuration = ScanFlowConfiguration().apply {
    structuredData = EnumSet.of(ScanFlowConfiguration.StructuredData.BARCODE)
    structuredDataBarcodeTypes = EnumSet.of(Barcode.Type.EAN13)
    multiPage = false
    skipPostProcessingScreen = true
}

Start the scan flow with the above configuration

You can now start the scan flow.

// Keep a strong reference on ScanFlow
let scanFlow = GSKScanFlow(configuration: configuration)

do {
    let result = try await scanFlow.resultByStarting(fromViewController: viewController)
    handleResult(result)
} catch {
    handleError(error)
}
val scanLauncher = registerForActivityResult(ScanActivity.Contract()) { output ->
    when (output) {
        is FlowOutput.Success -> handleResult(output.result)
        is FlowOutput.Error -> handleError(output.error)
    }
}

scanLauncher.launch(configuration)

Handle the success

We can now write the result handler. Since this is a demo, we will just output the extracted data but in your real application you will likely display the data to the user for validation, and then serialize it to send it to your backend.

func handleResult(_ result: GSKScanFlowResult) {
    // We only consider the first scan since we are in single-page mode
    guard let scan = result.scans.first else {
        print("Unexpected error")
        return
    }

    // Check if there is structured data
    guard let structuredResult = scan.structuredData else {
        print("No structured data result")
        return
    }

    // Check if barcodes have been detected
    let barcodes = structuredResult.barcodes
    if barcodes.isEmpty {
        print("No barcodes detected")
        return
    }

    for barcode in barcodes {
        print("Type: \(barcode.type)")
        print("Value: \(barcode.value)")
    }
}
fun handleResult(result: ScanFlowResult) {
    // We only consider the first scan since we are in single-page mode
    val scan = result.scans?.firstOrNull() ?: run {
        Log.e("Scanner", "Unexpected error")
        return
    }

    // Check if there is structured data
    val structuredResult = scan.structuredDataResult ?: run {
        Log.d("Scanner", "No structured data result")
        return
    }

    // Check if barcodes have been detected
    val barcodes = structuredResult.barcodes
    if (barcodes.isEmpty()) {
        Log.d("Scanner", "No barcodes detected")
        return
    }

    for (barcode in barcodes) {
        Log.d("Scanner", "Type: ${barcode.type}")
        Log.d("Scanner", "Value: ${barcode.value}")
    }
}

Error handling

It’s important to handle errors properly. There are two important cases:

  • The scan flow is cancelled by the user
  • The Genius Scan SDK license is expired
func handleError(_ error: Error) {
    let nsError = error as NSError
    if nsError.domain == GSKScanFlowErrorDomain && nsError.code == GSKScanFlowErrorCode.cancellation.rawValue {
        // User cancelled - do nothing
    } else {
        // Show a UIAlertController with error.localizedDescription
        let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        viewController.present(alert, animated: true)
    }
}
fun handleError(error: ScanFlowError) {
    if (error.code == ScanFlowErrorCode.CANCELLATION) {
        return
    }

    // Show an AlertDialog with error message
    AlertDialog.Builder(activity)
        .setTitle("Error")
        .setMessage(error.message)
        .setPositiveButton("OK") { dialog, _ -> dialog.dismiss() }
        .show()
}

Launching the app

You can now build and run the application on an actual device to test the barcode scanning:

  • Build and run on a physical iOS device (the iOS simulator doesn’t support camera)
  • Point the camera at a document with a barcode
  • The SDK will detect and extract the barcode data
  • Build and run on a physical Android device
  • Point the camera at a document with a barcode
  • The SDK will detect and extract the barcode data

Ready to get started?

Start with a free trial license to test the SDK, or contact us directly for a custom quote tailored to your needs.

Products

Industries

Case Studies

Integration

Company

© 2026 The Grizzly Labs. All rights reserved.