How to extract QR-codes and barcodes from documents

Available on: iOS Android

Note: This guide covers scanning readable codes 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 readable codes on documents. This enables you to get both a clean image of the document as well as structured data representing the readable codes 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 readable code 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 = .readableCode
configuration.structuredDataReadableCodeTypes = [.ean13]
configuration.multiPage = false
configuration.skipPostProcessingScreen = true
val configuration = ScanConfiguration().apply {
    structuredData = EnumSet.of(StructuredData.READABLE_CODE)
    structuredDataReadableCodeTypes = EnumSet.of(ReadableCodeType.EAN_13)
    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)
}
// Start scan flow
ScanFlow.scanWithConfiguration(activity, configuration)

// Handle result in onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    try {
        val result = ScanFlow.getScanResultFromActivityResult(data)
        handleResult(result)
    } catch (e: Exception) {
        handleError(e)
    }
}

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.structuredDataResult else {
        print("No structured data result")
        return
    }

    // Check if readable codes have been detected
    guard let readableCodes = structuredResult.readableCodes else {
        print("No readable codes detected")
        return
    }

    for readableCode in readableCodes {
        print("Type: \(readableCode.type)")
        print("Value: \(readableCode.value)")
    }
}
fun handleResult(result: ScanResult) {
    // 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 readable codes have been detected
    val readableCodes = structuredResult.readableCodes ?: run {
        Log.d("Scanner", "No readable codes detected")
        return
    }

    for (readableCode in readableCodes) {
        Log.d("Scanner", "Type: ${readableCode.type}")
        Log.d("Scanner", "Value: ${readableCode.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) {
    if (error as NSError).domain == GSKScanFlowErrorDomain && (error as NSError).code == GSKScanFlowError.userCancellation.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: Exception) {
    // 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

Products

Industries

Case Studies

Integration

Company

© 2025 The Grizzly Labs. All rights reserved.