Scanbot SDK has been acquired by Apryse! Learn more

Learn more
Skip to content

How to build an iOS MICR scanner app in Swift

Kevin August 11, 2025 8 mins read
iOS Data Capture SDK

In this tutorial, we’ll use Xcode, Swift, and the Scanbot Check Scanner SDK to create an iOS app for extracting the routing number, account number, and check number from the MICR (magnetic ink character recognition) line on paper checks.

Scanning the MICR line on a check with our iOS MICR scanner app

We’ll achieve this in just four easy steps:

  1. Preparing the project
  2. Setting up the main screen
  3. Implementing the MICR code scanning feature
  4. Setting the expected check formats

All you need is a Mac with the latest version of Xcode and a test device, since we’ll need to use the camera.

Want to see the final code right away? Click here.

ViewController.swift:

import UIKit
import ScanbotSDK

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func scanMicrCodeButtonTapped(_ sender: UIButton) {
        let configuration = SBSDKUI2CheckScannerScreenConfiguration()
        SBSDKUI2CheckScannerViewController.present(on: self,
                                                  configuration: configuration) { [weak self] result in
            guard let self = self else { return }

            var alertMessage = ""

            if let result = result {
                if let genericDocument = result.check, let wrapper = genericDocument.wrap() {
                    if let indCheck = wrapper as? SBSDKCheckDocumentModelINDCheck {
                        if let routingNumber = indCheck.sortNumber?.value {
                            alertMessage += "Routing number: \(routingNumber.text)\n"
                        } else {
                            alertMessage += "Routing number not found.\n"
                        }
                        if let accountNumber = indCheck.accountNumber?.value {
                            alertMessage += "Account number: \(accountNumber.text)\n"
                        } else {
                            alertMessage += "Account number not found.\n"
                        }
                        if let checkNumber = indCheck.serialNumber?.value {
                            alertMessage += "Check number: \(checkNumber.text)\n"
                        } else {
                            alertMessage += "Check number not found.\n"
                        }
                    } else if let unknownCheck = wrapper as? SBSDKCheckDocumentModelUnknownCheck {
                        alertMessage += "Unknown check format detected.\n"
                        if let fields = genericDocument.allFields(includeEmptyFields: false) {
                            if fields.isEmpty {
                                alertMessage += "No fields found for unknown check.\n"
                            } else {
                                for field in fields {
                                    if let value = field.value {
                                        alertMessage += "\(field.type.name): \(value.text)\n"
                                    } else {
                                        alertMessage += "Field \(field.type.name) has no value.\n"
                                    }
                                }
                            }
                        } else {
                            alertMessage += "Could not retrieve all fields for unknown check.\n"
                        }
                    } else {
                        alertMessage += "Check wrapper is of an unhandled type.\n"
                        alertMessage += "Actual wrapper type: \(type(of: wrapper))\n"
                    }
                } else {
                    alertMessage += "No check document or wrapper found in result.\n"
                }
            } else {
                alertMessage += "Scan was canceled or no result was returned.\n"
            }
            let alertController = UIAlertController(title: "Scan Result", message: alertMessage, preferredStyle: .alert)
            let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
            alertController.addAction(okAction)
            self.present(alertController, animated: true, completion: nil)
        }
    }
}

Step 1: Prepare the project

Open Xcode and create a new iOS App project. Name the project (e.g., “iOS MICR Scanner”), choose Storyboard as the interface, and Swift as the language.

After opening your project, go to File > Add Package Dependencies… and add the Scanbot SDK package for the Swift Package Manager.

Open your Main App Target’s Info tab and add a Privacy – Camera Usage Description key with a value such as “Grant camera access to start scanning”.

In AppDelegate.swift, add a line for setting the license key to the application(_:didFinishLaunchingWithOptions:) method. We don’t need one for this tutorial, but if you have a license key you’d like to use, uncomment the code below and replace <YOUR_LICENSE_KEY> with your actual key.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Uncomment the following line and replace <YOUR_LICENSE_KEY> with your actual key
    // Scanbot.setLicense("<YOUR_LICENSE_KEY>")

    return true
}

Without a license key, the SDK will run in trial mode for 60 seconds per session. If you need more than that, you can generate a free trial license.

Step 2: Set up the main screen

We’ll now create a simple UI for starting our MICR Scanner.

First, open Main.storyboard and add a UIButton with a meaningful label (e.g., “Scan MICR Code”) to the main view.

After that, create an IBAction for the button in ViewController.swift and implement the button action to present the scanner view controller.

@IBAction func scanMicrCodeButtonTapped(_ sender: UIButton) {
    // Code to present the scanner view controller will go here.
}

We’ll also need to add the following import to ViewController.swift:

import ScanbotSDK

Step 3: Implement the MICR code scanning feature

This step involves presenting the Check Scanner view controller. The Scanbot SDK provides a pre-built UI for the scanning process.

Add the following code inside scanMicrCodeButtonTapped() in ViewController.swift:

@IBAction func scanMicrCodeButtonTapped(_ sender: UIButton) {

    // Create a configuration object for the check scanner screen.
    let configuration = SBSDKUI2CheckScannerScreenConfiguration()

    // Present the check scanner view controller modally.
    SBSDKUI2CheckScannerViewController.present(on: self,
                                              configuration: configuration) { [weak self] result in
        }
    }
}

Step 4: Set the expected check formats

Now we’ll need to process the scan result. You can start by safely unwrapping self to make sure the view controller still exists. Then, check if the result is not nil and if it contains a check document.

If a check document is found, you can attempt to wrap it as a specific type of check if you know which variations to expect. In this example, we’ll look for the Indian check format (SBSDKCheckDocumentModelINDCheck). For the full list of supported check formats, please refer to the SDK’s API documentation.

@IBAction func scanMicrCodeButtonTapped(_ sender: UIButton) {
    let configuration = SBSDKUI2CheckScannerScreenConfiguration()
    SBSDKUI2CheckScannerViewController.present(on: self,
                                              configuration: configuration) { [weak self] result in
        guard let self = self else { return } // safely unwrap self

        var alertMessage = ""

        if let result = result {
            // Attempt to retrieve the scanned check document and wrap it.
            if let genericDocument = result.check, let wrapper = genericDocument.wrap() {
                // Check if the scanned document is an Indian (IND) check.
                if let indCheck = wrapper as? SBSDKCheckDocumentModelINDCheck {
                    // We'll process the scan results here.
                }

Once we have the check information, we can extract the specific data we need. In this example, we’ll extract the routing number, account number, and check number. We’ll check if each of these fields exists before trying to access its value and then add the extracted information to a string that will be used to display the results.

if let indCheck = wrapper as? SBSDKCheckDocumentModelINDCheck {
    if let routingNumber = indCheck.sortNumber?.value {
        alertMessage += "Routing number: \(routingNumber.text)\n"
    } else {
        alertMessage += "Routing number not found.\n"
    }
    if let accountNumber = indCheck.accountNumber?.value {
        alertMessage += "Account number: \(accountNumber.text)\n"
    } else {
        alertMessage += "Account number not found.\n"
    }
    if let checkNumber = indCheck.serialNumber?.value {
        alertMessage += "Check number: \(checkNumber.text)\n"
    } else {
        alertMessage += "Check number not found.\n"
    }
}

If the check isn’t an Indian check, we’ll handle it as an unknown check format (SBSDKCheckDocumentModelUnknownCheck). In this case, we’ll iterate through all the available fields in the document and add their type and value to the results string.

} else if let unknownCheck = wrapper as? SBSDKCheckDocumentModelUnknownCheck {
    // Handle cases where the check format is unknown.
    alertMessage += "Unknown check format detected.\n"
    // Retrieve all available fields for the unknown check.
    if let fields = genericDocument.allFields(includeEmptyFields: false) {
        if fields.isEmpty {
            alertMessage += "No fields found for unknown check.\n"
        } else {
            // Iterate through the fields and print their type and value.
            for field in fields {
                if let value = field.value {
                    alertMessage += "\(field.type.name): \(value.text)\n"
                } else {
                    alertMessage += "Field \(field.type.name) has no value.\n"
                }
            }
        }
    } else {
        alertMessage += "Could not retrieve all fields for unknown check.\n"
    }
}

Finally, we present a UIAlertController to the user. This alert will show all the data collected from the scanned check to give the user immediate feedback on the success of the scan and the information that was extracted.

let alertController = UIAlertController(title: "Scan Result", message: alertMessage, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)

Your final ViewController.swift will then look like this:

import UIKit
import ScanbotSDK

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func scanMicrCodeButtonTapped(_ sender: UIButton) {
        let configuration = SBSDKUI2CheckScannerScreenConfiguration()
        SBSDKUI2CheckScannerViewController.present(on: self,
                                                  configuration: configuration) { [weak self] result in
            guard let self = self else { return }

            var alertMessage = ""

            if let result = result {
                if let genericDocument = result.check, let wrapper = genericDocument.wrap() {
                    if let indCheck = wrapper as? SBSDKCheckDocumentModelINDCheck {
                        if let routingNumber = indCheck.sortNumber?.value {
                            alertMessage += "Routing number: \(routingNumber.text)\n"
                        } else {
                            alertMessage += "Routing number not found.\n"
                        }
                        if let accountNumber = indCheck.accountNumber?.value {
                            alertMessage += "Account number: \(accountNumber.text)\n"
                        } else {
                            alertMessage += "Account number not found.\n"
                        }
                        if let checkNumber = indCheck.serialNumber?.value {
                            alertMessage += "Check number: \(checkNumber.text)\n"
                        } else {
                            alertMessage += "Check number not found.\n"
                        }
                    } else if let unknownCheck = wrapper as? SBSDKCheckDocumentModelUnknownCheck {
                        alertMessage += "Unknown check format detected.\n"
                        if let fields = genericDocument.allFields(includeEmptyFields: false) {
                            if fields.isEmpty {
                                alertMessage += "No fields found for unknown check.\n"
                            } else {
                                for field in fields {
                                    if let value = field.value {
                                        alertMessage += "\(field.type.name): \(value.text)\n"
                                    } else {
                                        alertMessage += "Field \(field.type.name) has no value.\n"
                                    }
                                }
                            }
                        } else {
                            alertMessage += "Could not retrieve all fields for unknown check.\n"
                        }
                    } else {
                        alertMessage += "Check wrapper is of an unhandled type.\n"
                        alertMessage += "Actual wrapper type: \(type(of: wrapper))\n"
                    }
                } else {
                    alertMessage += "No check document or wrapper found in result.\n"
                }
            } else {
                alertMessage += "Scan was canceled or no result was returned.\n"
            }
            let alertController = UIAlertController(title: "Scan Result", message: alertMessage, preferredStyle: .alert)
            let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
            alertController.addAction(okAction)
            self.present(alertController, animated: true, completion: nil)
        }
    }
}

Our Number Scanner iOS app is now ready. Build and run the app and scan a check …

Example of a paper check with an MICR code at the bottom

… to test your scanner!

Scanning the MICR line on a check with our iOS MICR scanner app

Conclusion

This completes the basic implementation of an MICR Scanner for iOS using Swift and the Scanbot SDK. We used the SDK’s Ready-to-Use UI Components for this tutorial, which you can further customize to fit your needs.

Should you have questions or run into any issues, we’re happy to help! Just shoot us an email via tutorial-support@scanbot.io.

Happy scanning! 🤳

Related blog posts

Experience our demo apps

Barcode Icon Art

Barcode Scanner SDK

Scan 1D and 2D barcodes reliably in under 0.04s. Try features like Batch Scanning, Scan & Count, and our AR Overlays.

Launch Web Demo

Scan the code to launch the web demo on your phone.

Web QR Code

Also available to download from:

Document Icon Art

Document Scanner SDK

Scan documents quickly and accurately with our free demo app. Create crisp digital scans in seconds.

Launch Web Demo

Scan the code to launch the web demo on your phone.

Black and white QR code. Scan this code for quick access to information.

Also available to download from:

Data_capture Icon Art

Data Capture Modules

Try fast, accurate data capture with our demo app. Extract data from any document instantly – 100% secure.

Launch Web Demo

Scan the code to launch the web demo on your phone.

Black and white QR code. Scan this quick response code with your smartphone.

Also available to download from: