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.

  1. Initialize a pod file in the project folder.

    pod init
    
  2. Add the following lines to the Podfile.

    pod 'DynamsoftLabelRecognizerBundle','3.2.3000'
    pod 'DynamsoftCodeParser','2.2.10'
    pod 'DynamsoftCodeParserDedicator','1.2.20'
    
  3. 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

  1. 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
    }
    
  2. Open the camera in the viewWillAppear function.

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        dce.open()
    }
    

Scan MRZ from the Camera Preview

  1. Import libraries.

    import DynamsoftCore
    import DynamsoftLabelRecognizer
    import DynamsoftCaptureVisionRouter
    import DynamsoftUtility
    import DynamsoftCodeParser
    
  2. Create a Capture Vision Router instance to call Label Recognizer to recognize the text and Code Parser to parse the text.

    private cvr = CaptureVisionRouter()
    
  3. Load MRZ models.

    1. Add the DynamsoftResources.bundle as a reference. You can find the files here.

      resources bundle

    2. Update the runtime settings for MRZ.

      try? cvr.initSettingsFromFile("PassportScanner.json")
      
  4. Set the input of Capture Vision Router to Camera Enhancer so that the router can fetch camera frames for processing.

    try? cvr.setInput(dce)
    
  5. 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