Skip to main content

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.

Document key

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.


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.

Custom display messages

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

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

UIKit

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)

Custom display messages

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)

SwiftUI

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.


Reading the result

OzoneNFCDocument is the object returned on a successful read.

Always populated:

PropertyTypeDescription
firstNameStringGiven name(s)
lastNameStringSurname
documentNumberStringDocument number
documentTypeStringRaw document type code (e.g. "P")
documentSubTypeStringDocument sub-type as encoded in the MRZ
documentCodeStringFull document code string from the MRZ
translatedDocumentTypeTranslatedDocumentTypeEnum — see below
dateOfBirthStringyyMMdd
expiryDateStringyyMMdd
genderStringSex as encoded in the MRZ — typically "M", "F", or "<" (unspecified)
nationalityStringISO 3166-1 alpha-3 country code
issuingAuthorityStringName or code of the authority that issued the document
passportDataValidBooltrue if all data group hashes match the SOD

Optional:

PropertyTypeDescription
isExpiredBool?Calculated from expiryDate when the result is created; true if expired, false if not, nil if the date cannot be parsed
ageInt?Calculated from dateOfBirth; nil if the date cannot be parsed
personalNumberString?Personal number from the MRZ (field 14), if present
placeOfBirthString?Place of birth if present on the chip
imageUIImage?Facial image from DG2
signatureImageUIImage?Signature image from DG7, if present

Security status fields

Each authentication step exposes an AuthStatus: .success, .failure, or .skipped (not attempted).

PropertyDescription
BACStatusBasic Access Control
PACEStatusPassword Authenticated Connection Establishment
chipAuthenticationStatusChip Authentication (anti-cloning)
activeAuthenticationStatusActive Authentication (anti-substitution)

TranslatedDocumentType

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

Chip position helper

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.

Error handling

OzoneNFCError covers the expected failure cases:

CaseDescription
.invalidNfcTagThe tag found is not a valid eMRTD chip
.invalidNfcKeyFormattingThe document key (MRZ data) is malformed
.userCancelledThe user dismissed the NFC sheet
.unexpectedErrorAn unrecoverable internal error occurred