Skip to content

Usage Guide ​

Quick Start ​

Import the SDK and start the scanner with a result delegate:

swift
import UIKit
import KinegramScanSDK

class ViewController: UIViewController {
    let scanSdk = KSCScanSDK() 

    @IBAction func buttonStartScannerTouched(_ sender: UIButton) {
        scanSdk.startScanner(withDelegate: self, title: "ScanSDK Demo") 
    }
}

extension ViewController: KSCResultDelegate {
    func found(_ results: [KSCResult]) {
        for result in results {
            print("Scanned result: \(result)")
        }
    }
}

Processing Results ​

An array of KSCResult objects is returned by the delegate method. Each KSCResult contains the recognized data along with its type, defined by the KSCResultType enum:

swift
extension ExampleViewController: KSCResultDelegate {
    func found(_ results: [KSCResult]) {
        for result in results {
            switch result.type {
            case .MRZ:
                if let mrz = result.mrz {
                    print("MRZ found: \(result.text)")
                    print(" Document Code: \(mrz.documentCode)")
                    print(" Issuing State: \(mrz.issuingState)")
                    print(" Primary Identifier: \(mrz.primaryIdentifier)")
                    print(" Secondary Identifier: \(mrz.secondaryIdentifier)")
                    print(" Nationality: \(mrz.nationality)")
                    print(" Document Number: \(mrz.documentNumber)")
                    print(" Date of birth: \(mrz.dateOfBirth)")
                    print(" Sex: \(mrz.sex)")
                    print(" Date of expiry: \(mrz.dateOfExpiry)")
                    print(" Optional Data 1: \(mrz.optionalData1)")
                    print(" Optional Data 2: \(mrz.optionalData2)")
                    print(" Blank Number: \(mrz.blankNumber)")
                    print(" Language: \(mrz.language)")

                    // Handle MRZ validation errors (see MRZError enum)
                    for error in mrz.errors {
                        if let e = MRZError(rawValue: error.intValue) {
                            print(" Error: \(e)")
                        }
                    }
                }

            case .NumberPlate:
                if let numberPlate = result.numberPlate {
                    print("NumberPlate found: \(numberPlate.text)")
                    print(" First part: \(numberPlate.firstPart)")
                    print(" Second part: \(numberPlate.secondPart)")
                    print(" Country: \(numberPlate.country)")
                }

            case .IDL:
                if let idl = result.idl {
                    print("IIN found: \(idl.iin)")
                    for key in idl.order {
                        if let key = key as? String, let value = idl.elements[key] {
                            print("\(key): \(value)")
                        }
                    }
                }

            case .Barcode:
                print("Barcode format: \(result.format)")
                print("Barcode text: \(result.text)")

            case .IDB:
                if let idb = result.idb {
                    print("IDB found:")
                    print("Country ID: \(idb.header.countryId)")
                    // See class KSCIDBInfo for more information
                    print("Signature Valid: \(idb.signatureValid)")
                }

            case .VDS:
                if let vds = result.vds {
                    print("VDS found: \(vds.name)")
                    print("Country ID: \(vds.header.countryId)")
                    // See class KSCVDSInfo for more information
                    print("Signature Valid: \(vds.signatureValid)")
                }

            case .VDSNC:
                if let vdsnc = result.vdsNc {
                    print("VDS-NC found:")
                    print("Country ID: \(vdsnc.header.issuingCountry)")
                    // See class KSCVDSNCInfo for more information
                    print("Signature Valid: \(vdsnc.signatureValid)")
                }

            case .Seal:
                if let seal = result.seal {
                    print("Seal found: \(seal.format)")
                    print("Name: \(seal.name)")
                    // See class KSCSealInfo for more information
                    print("Signature Valid: \(seal.signatureValid)")
                }

            default:
                print(result.text)
            }
        }
    }
}

Continuous Scanning ​

By default the scanner closes itself as soon as it finds a result. To keep it running and decide yourself whether to continue or stop — for example to show an alert for some number plates but not others — implement foundResults:continueScanning: instead of foundResults:. The scanner pauses and waits for you to call the completion block:

swift
extension ExampleViewController: KSCResultDelegate {
    func found(_ results: [KSCResult], continueScanning completion: @escaping (Bool) -> Void) {
        if let plate = results.first(where: { $0.type == .NumberPlate }), shouldAlert(plate) {
            let alert = UIAlertController(
                title: "Scan Result",
                message: "Flagged plate: \(plate.numberPlate?.text ?? "")",
                preferredStyle: .alert
            )
            alert.addAction(UIAlertAction(title: "Continue", style: .default) { _ in
                completion(true)
            })
            alert.addAction(UIAlertAction(title: "Stop", style: .cancel) { _ in
                completion(false)
            })
            present(alert, animated: true)
        } else {
            completion(true)
        }
    }
}

If both delegate methods are implemented, foundResults:continueScanning: takes precedence. Note that the same object may be reported again once scanning resumes if it is still in view — debounce on the fields you care about (e.g. numberPlate.text) if that matters for your use case.

MRZ Validation Errors ​

The mrz.errors property contains an array of NSNumber values representing validation errors found during MRZ parsing. Convert them to MRZError enum values for easier handling:

swift
for error in mrz.errors {
    if let e = MRZError(rawValue: error.intValue) {
        switch e {
        case .ErrorDocumentNumberCheckDigit:
            print("Document number check digit invalid")
        case .ErrorDateOfBirthCheckDigit:
            print("Date of birth check digit invalid")
        case .ErrorDateOfExpiryCheckDigit:
            print("Date of expiry check digit invalid")
        case .ErrorCombinedCheckDigit:
            print("Combined check digit invalid")
        // Handle other cases…
        default:
            break
        }
    }
}

See the MRZError enum in KSCMRZInfo.h for the complete list of error types.

Custom IDB Profiles ​

Use setIDBProfiles() to set custom IDB profiles before scanning:

swift
scanSdk.setIDBProfiles(
    "[{\"tag\":9,\"name\":\"Card Access Number\",\"type\":\"C40STR\"}]"
)

Custom VDS Profiles ​

Use setVDSProfiles() to specify custom VDS profiles:

swift
scanSdk.setVDSProfiles(
    "[{\"id\":4128,\"features\":[{\"tag\":2,\"name\":\"foo\",\"type\":\"UTF8STR\"}]}]"
)

The integer key (4128 in this example, which is 0x1020 hexadecimal; JSON does not support hexadecimal numbers) must be the "Document Feature Definition Reference" byte (0x10 in bit 9 to 16) and the "Document Type Category" byte (0x20 in bit 1 to 8). These bytes identify a VDS profile.

Document Feature Types ​

For each document feature you need to specify a tag, a name, and a type. Possible types are:

TypeDescription
RAWBinary data
UBYTEUnsigned char
UINT16LEUnsigned 16-bit integer little endian
UINT24LEUnsigned 24-bit integer little endian
UINT32LEUnsigned 32-bit integer little endian
DATEUnsigned 24-bit integer big endian read as "MMddyyyy"
TIMESTAMPUnix time stamp
UTF8STRUTF8 string
C40STRC40-encoded string
C40MRZMRVAC40-encoded MRVA MRZ
C40MRZMRVBC40-encoded MRVB MRZ
C40MRZTD2C40-encoded TD2 MRZ
C40MRZTD3C40-encoded TD3 MRZ

VDS Verification ​

To verify a VDS, you need the certificate with which the VDS was signed.

Use setCertificates() to specify a list of certificate files before scanning:

swift
scanSdk.setCertificates(["path/to/certificate.cer"])

VDS-NC / IDB Verification ​

To verify a VDS-NC or IDB barcode, the CSCA (Country Signing Certificate Authority) certificate of the issuing country is required. This certificate can be obtained from the ICAO master list or the BSI master list.

Use setCMS() to specify a list of master list files before scanning:

swift
scanSdk.setCMS(["path/to/masterlist.ml"])

Seal Verification ​

A proprietary seal (2D-Doc, CHECK-AT, BE-SEAL) is signed either with a certificate or with a public key. seal.trustRequirement tells what the seal needs:

trustRequirementTrust material
.certificateThe signing certificate, set with setCertificates()
.publicKeyThe public key, set with setPublicKeys()
.noneThe SDK cannot verify the seal

CHECK-AT seals are signed with a public key. Use setPublicKeys() to specify a list of public key files before scanning:

swift
scanSdk.setPublicKeys(["path/to/public-key.pem"])

If the SDK cannot verify a seal, seal.signedData, seal.digest and seal.signature let you verify it elsewhere.

Verification Status ​

Every VDS, VDS-NC, IDB and seal result has a verification property. Its status is the result of verifying the signature against the certificates, master lists and public keys you set. The statuses follow the validation policy of ICAO Doc 9303-13, Appendix D:

StatusMeaning
.validThe signature is valid, the certificate is trusted and it is valid at the time of verification.
.expiredCertificateThe signature is valid and the certificate is trusted, but the certificate or a CSCA of its chain is not valid at the time of verification.
.unknownCertificateNo given certificate or key verifies the signature, and none of them is the one the seal references.
.untrustedCertificateThe signature is valid, but the certificate embedded in the seal neither chains to a given CSCA nor is a given certificate.
.invalidSignatureThe signature does not match the certificate the seal references or embeds, or the embedded certificate cannot be read.
.unsignedThe seal carries no signature.

signatureValid is true only if the status is .valid. verification.certificate holds the DER-encoded certificate the signature was checked with. It is empty if no certificate verified the signature or a public key was used.

swift
if let vds = result.vds {
    switch vds.verification.status {
    case .valid:
        print("The VDS is valid")
    case .expiredCertificate:
        print("The certificate has expired")
    case .unknownCertificate:
        print("No certificate or key you set verifies the signature")
    case .untrustedCertificate:
        print("The certificate is not trusted")
    default:
        print("The VDS is not valid")
    }
}