DLens SDK Documentation
DLens is a production-grade iOS SDK for scanning driver licences, passports, and national ID cards — entirely on-device, with no data stored or transmitted.
Overview
DLens uses the iOS Vision framework and AVFoundation to detect and parse identity documents directly on device. No network connection is required. No document data leaves the device at any point.
The SDK supports three scanning pipelines:
- AVCaptureMetadataOutput — hardware ISP barcode decoder for PDF417 (primary, fastest)
- Vision VNDetectBarcodesRequest — software fallback for difficult lighting conditions
- Vision VNRecognizeTextRequest — ICAO MRZ OCR for passports and national IDs
Requirements
- iOS 17.0 or later
- Xcode 15.0 or later
- Swift 5.9 or later
- A valid DLens licence key (request one here)
NSCameraUsageDescriptionkey in yourInfo.plist
Installation
XCFramework (Recommended)
Download the framework
Request your trial key at dlens.io/#contact — we'll send you DLensSDK.xcframework along with your key.
Add to your project
Drag DLensSDK.xcframework into your Xcode project navigator. In the dialog, check Copy items if needed.
Embed & Sign
In your target's General → Frameworks, Libraries, and Embedded Content, set DLensSDK.xcframework to Embed & Sign.
Add camera permission
Add NSCameraUsageDescription to your Info.plist with a description explaining why your app needs camera access.
💡 Tip: The framework must be Embed & Sign, not just Do Not Embed. Without embedding, the framework won't be copied into your app bundle and the install will fail with a missing signature error.
Quick Start
Three steps to your first scan:
import SwiftUI import DLensSDK // Step 1: Initialise at app launch @main struct MyApp: App { init() { try? DLensKit.initialize( apiKey: "YOUR_API_KEY", secret: "YOUR_SECRET" ) } var body: some Scene { WindowGroup { ContentView() } } } // Step 2: Present the scanner struct ContentView: View { @State private var isScanning = false var body: some View { Button("Scan Licence") { isScanning = true } .fullScreenCover(isPresented: $isScanning) { // Step 3: Handle the result DLensScanner { result in print(result.fullName) print(result.licenseNumber) isScanning = false } } } }
Initialisation
Call DLensKit.initialize() once at app launch — typically in your App.init() or AppDelegate.application(_:didFinishLaunchingWithOptions:). Calling it more than once is safe and is a no-op after the first successful call.
import DLensSDK do { try DLensKit.initialize( apiKey: "YOUR_API_KEY", secret: "YOUR_SECRET", configuration: { var c = DLensConfiguration() c.showTorchButton = true c.showZoomControls = true c.instructionText = "Aim at the barcode on the back" return c }() ) } catch { // DLensError.invalidKey — key is malformed or expired // DLensError.bundleMismatch — key locked to a different bundle ID print("DLens init failed: \(error.localizedDescription)") }
SwiftUI Integration
Use DLensScanner inside a .fullScreenCover or .sheet. It fills the presented area with the camera UI automatically.
DLensScanner( onResult: { result in // Called on main thread when scan succeeds self.scanResult = result self.isScanning = false }, onError: { error in // Camera permission denied, key expired, etc. print(error.localizedDescription) self.isScanning = false }, onCancel: { // User tapped the cancel/back button self.isScanning = false } )
UIKit Integration
let scanner = DLensScannerViewController( onResult: { [weak self] result in self?.handleResult(result) }, onError: { error in print(error) } ) present(scanner, animated: true)
Configuration
| Property | Type | Default | Description |
|---|---|---|---|
| showTorchButton | Bool | true | Show the torch toggle button in the camera UI |
| showZoomControls | Bool | true | Show manual +/− zoom buttons |
| instructionText | String | "Aim at barcode…" | Instruction label shown below the viewfinder |
| timeoutSeconds | TimeInterval | 60 | Seconds before the scanner auto-dismisses without a result |
| accentColor | Color | .blue | Tint colour for UI elements |
DLensScanResult
All fields are optional String? or Date? unless marked otherwise.
| Field | Type | Description |
|---|---|---|
| firstName | String | Given name — always present |
| lastName | String | Surname — always present |
| middleName | String? | Middle name (AAMVA only) |
| fullName | String | Computed: firstName + lastName |
| dateOfBirth | Date? | Parsed date of birth |
| formattedDateOfBirth | String? | DOB formatted as "MMM d, yyyy" |
| licenseNumber | String | Document/licence number |
| licenseClass | String? | Vehicle class codes (AAMVA) |
| restrictions | String? | Driving restrictions (AAMVA) |
| endorsements | String? | Driving endorsements (AAMVA) |
| expiryDate | Date? | Document expiry date |
| issueDate | Date? | Document issue date |
| streetAddress | String? | Street address (AAMVA) |
| city | String? | City (AAMVA) |
| state | String? | State/Province code (AAMVA) |
| postalCode | String? | Postal/ZIP code (AAMVA) |
| country | String? | Country code |
| gender | String? | "M", "F", or "X" |
| eyeColor | String? | Eye colour code (AAMVA) |
| height | String? | Height string (AAMVA) |
| rawPayload | String | Raw barcode/MRZ string |
Errors
| Error Case | Description |
|---|---|
| DLensError.notInitialized | DLensKit.initialize() was not called before presenting the scanner |
| DLensError.invalidKey | The API key is malformed, expired, or has been revoked |
| DLensError.bundleMismatch | The key was issued for a different app bundle ID |
| DLensError.cameraPermissionDenied | The user denied camera access. Direct them to Settings. |
Licence Keys
DLens uses HMAC-SHA256 signed JSON tokens. Each key encodes your bundle ID, expiry date, and a signature that can be verified entirely on-device without a network call.
⚠️ Keep your secret private. Never commit your API key or secret to a public repository. For production apps, load credentials from your app's secure configuration — not hardcoded in source.
For development, you can generate test credentials that work without a paid licence:
// Generate dev credentials (for testing only) let (apiKey, secret) = DLensKit.makeTestCredentials() try DLensKit.initialize(apiKey: apiKey, secret: secret)
Driver Licences
DLens parses AAMVA-standard PDF417 barcodes from all 50 US states, Washington DC, and all Canadian provinces and territories. Both legacy AAMVA 2000 and modern ANSI D20 formats are supported with automatic version detection.
Scan the back of the licence where the PDF417 barcode is located. The scanner automatically activates hardware barcode detection via AVCaptureMetadataOutput for maximum speed.
Passports
DLens reads the Machine-Readable Zone (MRZ) on the photo data page of ICAO TD3-format passports from 190+ countries. Full ICAO 9303 check digit validation is performed on all fields before returning a result.
Scan the bottom two lines of the photo page (the MRZ zone). The scanner uses Vision framework OCR with a two-strategy parser to handle real-world imperfections.
National ID Cards
TD1 (three-line, 30 chars) and TD2 (two-line, 36 chars) MRZ formats are supported, covering EU national ID cards, Middle Eastern national IDs, biometric residence permits, and many other document types.
Developer FAQ
The scanner is not finding my barcode. What should I try?
Ensure good lighting — the auto-zoom sweep will step from 1× to 2.5× automatically. Make sure the barcode is flat and not folded. Very worn or damaged barcodes may fall back to Vision software decoding which takes slightly longer.
Can I customise the scanner UI appearance?
Yes — use DLensConfiguration.accentColor to change the tint colour, and instructionText to customise the prompt. Full UI theming is available on Enterprise plans.
How do I handle camera permission denial?
Catch DLensError.cameraPermissionDenied in your onError handler and redirect the user to UIApplication.openSettingsURLString.
Does the SDK work offline?
Yes. All scanning is on-device and requires no internet connection. Licence validation is cached locally on first use and re-validated periodically when a connection is available.