How to Build an iOS MRZ Scanner with Swift
Dynamsoft Label Recognizer is an SDK to recognize the text of key areas. Its major use case is to read MRZ (machine-readable zone) on passports.
In this article, we are going to build an iOS MRZ scanner using Dynamsoft Label Recognizer with Swift.
A demo video of the final result:
New Project
Open Xcode to create a new UIKit project in Swift.
Since we are going to design the UIs programmatically, we can just delete Main.storyboard
and SceneDelegate.swift
and update the project and Info.plist
to remove relevant info.
Add Dependencies
Here, we use CocoaPods to manage dependencies.
-
Initialize a pod file in the project folder.
pod init
-
Add the following lines to the
Podfile
.pod 'DynamsoftLabelRecognizerBundle','3.2.3000' pod 'DynamsoftCodeParser','2.2.10' pod 'DynamsoftCodeParserDedicator','1.2.20'
-
Run
pod install
Add Permissions
We have to add the following to Info.plist
for the permissions to access the camera.
<key>NSCameraUsageDescription</key>
<string>For camera usage</string>
Initialize the License of Dynamsoft Label Recognizer
In AppDelegate.swift
, add the following to initialize the license of Dynamsoft Label Recognizer. You can apply for a 30-day trial license here.
@main
class AppDelegate: UIResponder, UIApplicationDelegate, LicenseVerificationListener {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let trialLicense = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //one-day public trial
LicenseManager.initLicense(trialLicense, verificationDelegate:self)
return true
}
func onLicenseVerified(_ isSuccess: Bool, error: Error?) {
print(isSuccess)
if(error != nil)
{
if let msg = error?.localizedDescription {
print("Server license verify failed, error:\(msg)")
}
}
}
}
Next, we are going to implement the pages in steps. There are two pages: home page and camera page.
Home Page
Add a Scan MRZ
button to navigate to the camera page.
class ViewController: UIViewController {
var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.button = UIButton(frame: .zero)
self.button.setTitle("Scan MRZ", for: .normal)
self.button.setTitleColor(.systemBlue, for: .normal)
self.button.setTitleColor(.lightGray, for: .highlighted)
self.button.addTarget(self,
action: #selector(buttonAction),
for: .touchUpInside)
self.navigationItem.title = "Home"
self.view.backgroundColor = UIColor.white
self.view.addSubview(self.button)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let button = self.button {
let width: CGFloat = 300
let height: CGFloat = 50
let x = view.frame.width/2 - width/2
let y = view.frame.height - 100
button.frame = CGRect.init(x: x, y: y, width: width, height: height)
}
}
@objc
func buttonAction() {
self.navigationController?.pushViewController(CameraController(), animated: true)
}
}
Since we are not using storyboard, we have to display the home page programmatically in AppDelegate.swift
with the following code.
@main
class AppDelegate: UIResponder, UIApplicationDelegate, LicenseVerificationListener {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let vc = ViewController()
let navController = UINavigationController(rootViewController: vc)
window?.rootViewController = navController
window?.makeKeyAndVisible()
//...
return true
}
}
Camera Page
Create a new camera view controller named CameraController.swift
. Then, we are going to start the camera and scan MRZ in this page.
Start the Camera using Camera Enhancer
-
Initialize an instance of camera view and add it to the view controller. Meanwhile, initialize an instance of camera enhancer to control the camera.
import DynamsoftCameraEnhancer private var dce: CameraEnhancer! private var dceView: CameraView! override func viewDidLoad() { super.viewDidLoad() configureDCE() } private func configureDCE() -> Void { dceView = CameraView(frame: self.view.bounds) dceView.scanLaserVisible = true dceView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.view.addSubview(dceView) dce = CameraEnhancer(view: dceView) dce.enableEnhancedFeatures(.frameFilter) //enable this feature to filter out blurry frames }
-
Open the camera in the
viewWillAppear
function.override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) dce.open() }
Scan MRZ from the Camera Preview
-
Import libraries.
import DynamsoftCore import DynamsoftLabelRecognizer import DynamsoftCaptureVisionRouter import DynamsoftUtility import DynamsoftCodeParser
-
Create a Capture Vision Router instance to call Label Recognizer to recognize the text and Code Parser to parse the text.
private cvr = CaptureVisionRouter()
-
Load MRZ models.
-
Add the
DynamsoftResources.bundle
as a reference. You can find the files here. -
Update the runtime settings for MRZ.
try? cvr.initSettingsFromFile("PassportScanner.json")
-
-
Set the input of Capture Vision Router to Camera Enhancer so that the router can fetch camera frames for processing.
try? cvr.setInput(dce)
-
Start processing and receive the parsed results after the camera is opened. After the result is extracted, go to the home page.
private var templateName = "ReadPassport" func configureCVR(){ //...... cvr.addResultReceiver(self) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) dce.open() cvr.startCapturing(templateName) } override func viewWillDisappear(_ animated: Bool){ super.viewWillDisappear(animated) self.cvr.stopCapturing() self.dce.clearBuffer() } func onParsedResultsReceived(_ result: ParsedResult) { // Deal with the parsed results. if (result.items?.count ?? 0 > 0) { ViewController.result = result DispatchQueue.main.async { self.navigationController?.popViewController(animated: true) } } }
Verify the Recognition Result
The recognition result of one frame may contain misrecognized characters. We can check the results of several frames to make sure the result is correct.
Here is the code to do this:
resultFilter = MultiFrameResultCrossFilter()
resultFilter.enableResultCrossVerification(.textLine, isEnabled: true)
cvr.addResultFilter(resultFilter)
Display Parsed MRZ Result
In the end, display the parsed MRZ result in a label.
var parsed = ""
parsed = parsed + "No.: " + result!.items![0].parsedFields["passportNumber"]! + "\n"
parsed = parsed + "Country.: " + result!.items![0].parsedFields["nationality"]! + "\n"
parsed = parsed + "Given names: " + result!.items![0].parsedFields["secondaryIdentifier"]! + "\n"
parsed = parsed + "Surname: " + result!.items![0].parsedFields["primaryIdentifier"]! + "\n"
parsed = parsed + "Date of birth: " + result!.items![0].parsedFields["dateOfBirth"]! + "\n"
self.label.text = parsed
Source Code
Get the source code of the demo to have a try: https://github.com/tony-xlh/iOS-MRZ-Scanner