As part of its structured data capabilities, the Genius Scan SDK is able to extract information from receipts. This enables you to get both a clean image of the receipt as well as structured data representing the information present on the receipt, extracted and ready to be processed by your system.
We recommend using the Genius Scan SDK if you are building a receipt scanning feature as part of an expense reporting app. This will let you extract and validate the data in the client before forwarding it to your backend for further processing. The receipt scanning feature also works well for extracting data from invoices.
The receipt or invoice digitization can extract:
In this guide, we will write a very simple receipt scanning app in a UIKit project. Writing this app should take about 10 minutes.
Add the Genius Scan SDK as a dependency to your project by following the section Integrating the framework from the Getting Started guide.
You will instantiate a scan flow with a specific configuration:
configuration.structuredData
to .receipt
to specify you are interested in scanning receipts.configuration.multiPage
to false
as generally you want the user to scan a single receipt and process it.skipPostProcessingScreen
to true
if you don’t want the user to review the receipt before processing it.let configuration = GSKScanFlowConfiguration()
configuration.structuredData = .receipt
configuration.multiPage = false
configuration.skipPostProcessingScreen = true
You can now start the scan flow.
Note: The Genius Scan SDK offers multiple convenient ways of starting the scan flow, in particular from UIKit view controllers or from SwiftUI views.
// Keep a strong reference on ScanFlow
let scanFlow = GSKScanFlow(configuration: configuration)
do {
let result = try await scanFlow.resultByStarting(fromViewController: viewController)
handleResult(result)
} catch {
handleError()
}
We can now write the handleResult
method. 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 a receipt has been detected
guard let receipt = structuredResult.receipt else {
print("No receipt detected")
return
}
// Use the detected data. Here we just print it, but we could
// make an API call.
print("Locale: \(receipt.locale?.identifier ?? "N/A")")
print("Merchant: \(receipt.merchant ?? "N/A")")
print("Amount: \(receipt.amount ?? 0.0)")
print("Currency: \(receipt.currency ?? "N/A")")
print("Date: \(receipt.date ?? Date())")
print("Category: \(receipt.category?.rawValue ?? "N/A")")
print("VAT Values: \(receipt.vatValues.map { "\($0)" }.joined(separator: ", "))")
}
It’s important to handle the error as well, there are two important cases:
func handleError(_ error: Error) {
if (error as NSError).domain == GSKScanFlowErrorDomain && (error as NSError).code == GSKScanFlowError.userCancellation.rawValue {
// Do nothing
} else {
// Show a UIAlertController with error.localizedDescription
}
}
You can now build and run the application on an actual device (the iOS simulator doesn’t support the camera) to test the receipt scanning.
© 2025 The Grizzly Labs. All rights reserved.