Skip to content
GBG Documents & Biometrics Documentation

Quick Setup

After completing the installation steps you are ready to read NFC documents. The overall process is: construct a document key from the MRZ data printed on the document, then use one of two approaches to initiate the scan and receive a result.

OzoneNFCDocumentKey is the access credential that unlocks the NFC chip. It is derived from the Machine Readable Zone (MRZ) printed on the document.

let key = OzoneNFCDocumentKey(
passportNumber: "AB1234567", // 9-character document number from MRZ
dateOfBirth: "900115", // yyMMdd — e.g. 15 Jan 1990
expiryDate: "260731" // yyMMdd — e.g. 31 Jul 2026
)

Date fields use the yyMMdd format (2-digit year, 2-digit month, 2-digit day), matching the MRZ encoding defined in ICAO Doc 9303.

Both dates are validated before scanning starts: if either is not a real calendar date in yyMMdd format, the read fails immediately with OzoneNFCError.invalidNfcKeyFormatting — no NFC session is opened. Validate or correct your MRZ data before constructing the key.


Approach 1 — Async/Await with OzoneNFCReader

Section titled “Approach 1 — Async/Await with OzoneNFCReader”

OzoneNFCReader provides a headless async API. The system NFC scanning sheet is presented automatically; your code resumes when reading completes or throws.

import OzoneNFC
let reader = OzoneNFCReader()
do {
let document = try await reader.readDocument(key)
// handle document
} catch let error as OzoneNFCError {
// handle known error
} catch {
// handle unexpected error
}

This is the simplest integration — the system NFC scanning sheet is managed automatically by the OS, so you only need to handle the result.

Pass a displayMessage closure to override the text shown during reading:

let document = try await reader.readDocument(key) { message in
switch message {
case .requestPresentPassport:
return "Hold your phone against the passport cover."
case .authenticatingWithPassport(let progress):
return "Authenticating… \(progress)%"
case .readingDataGroupProgress(let group, let progress):
return "Reading \(group)… \(progress)%"
case .successfulRead:
return "Done!"
default:
return nil // use default text
}
}

Approach 2 — Built-in UI with OzoneNFCViewController

Section titled “Approach 2 — Built-in UI with OzoneNFCViewController”

OzoneNFCViewController is a UIViewController that bundles a full guided scanning UI. Use it when you want the built-in visual flow.

import OzoneNFC
let vc = OzoneNFCViewController(documentKey: key) { result in
switch result {
case .success(let document):
// handle document
case .failure(let error):
// handle error
}
}
present(vc, animated: true)

OzoneNFCViewController accepts the same optional displayMessage closure as OzoneNFCReader. Pass it to the initializer to override the text shown during scanning:

let vc = OzoneNFCViewController(documentKey: key, displayMessage: { message in
switch message {
case .requestPresentPassport:
return "Hold your phone against the passport cover."
case .authenticatingWithPassport(let progress):
return "Authenticating… \(progress)%"
case .readingDataGroupProgress(let group, let progress):
return "Reading \(group)… \(progress)%"
case .successfulRead:
return "Done!"
default:
return nil // use default text
}
}) { result in
// handle result
}
present(vc, animated: true)

Wrap OzoneNFCViewController in a UIViewControllerRepresentable:

import OzoneNFC
import SwiftUI
struct NFCScanView: UIViewControllerRepresentable {
let documentKey: OzoneNFCDocumentKey
let completion: (Result<OzoneNFCDocument, OzoneNFCError>) -> Void
func makeUIViewController(context: Context) -> OzoneNFCViewController {
OzoneNFCViewController(documentKey: documentKey, completion: completion)
}
func updateUIViewController(_ uiViewController: OzoneNFCViewController, context: Context) {}
}

Present it using a NavigationLink or .fullScreenCover.


OzoneNFCDocument is the object returned on a successful read.

Always populated:

Property Type Description
firstName String Given name(s)
lastName String Surname
documentNumber String Document number
documentType String Raw document type code (e.g. "P")
documentSubType String Document sub-type as encoded in the MRZ
documentCode String Full document code string from the MRZ
translatedDocumentType TranslatedDocumentType Enum — see below
dateOfBirth String yyMMdd
expiryDate String yyMMdd
gender String Sex as encoded in the MRZ — typically "M", "F", or "<" (unspecified)
nationality String ISO 3166-1 alpha-3 country code
issuingAuthority String Name or code of the authority that issued the document
passportDataValid Bool true if all data group hashes match the SOD

Optional:

Property Type Description
isExpired Bool? Calculated from expiryDate when the result is created; true if expired, false if not, nil if the date cannot be parsed
age Int? Calculated from dateOfBirth; nil if the date cannot be parsed
personalNumber String? Personal number from the MRZ (field 14), if present
placeOfBirth String? Place of birth if present on the chip
image UIImage? Facial image from DG2
signatureImage UIImage? Signature image from DG7, if present

Each authentication step exposes an AuthStatus: .success, .failure, or .skipped (not attempted, for example because the chip doesn’t support it).

Property Description
BACStatus Basic Access Control
PACEStatus Password Authenticated Connection Establishment
chipAuthenticationStatus Chip Authentication (anti-cloning)
activeAuthenticationStatus Active Authentication (anti-substitution)

A failed security step doesn’t fail the read. If the chip fails Chip Authentication or Active Authentication, the read still completes and returns the document data, with that step’s status set to .failure. Check these statuses, together with passportDataValid, before you rely on the result.

public enum TranslatedDocumentType: String {
case `default`
case nationalPassport
case emergencyPassport
case diplomaticPassport
case officialOrServicePassport
case refugeePassport
case alienPassport
case statelessPassport
case travelDocument
case militaryPassport
}

OzoneNFCReader exposes a static helper to determine where the NFC chip is located on a document, which can be useful for displaying guidance to the user:

let position = OzoneNFCReader.getChipPositionFor(countryCode: "GBR")

Returns a value describing the expected chip location for the given ISO 3166-1 alpha-3 country code, or nil if the location is unknown.

OzoneNFCError covers the expected failure cases:

Case Description
.invalidNfcTag The tag found is not a valid eMRTD chip
.invalidNfcKeyFormatting The document key (MRZ data) is malformed — including a dateOfBirth or expiryDate that is not a valid yyMMdd date; raised before any NFC session is opened
.userCancelled The user dismissed the NFC sheet
.unexpectedError An unrecoverable internal error occurred