RoxyNFCScanner is a Swift Package for host-controlled NFC passport and ID scanning on iOS. Your app collects the MRZ input, starts the scan from its own UI, and receives the result through a delegate.
- iOS 14+ for CoreNFC scan flows
- Swift 5+ (
swift-tools-version: 5.10) - Xcode 16+
Developed with Xcode 26.3 and Swift 6.2.
This project was vibe coded with OpenAI Codex and Claude Code.
Add the package to Xcode with the repository URL, then import RoxyNFCScanner in the host app target that starts scans.
- CSCA certificate setup is only for passive authentication.
- If you do not provide a CSCA source URL, scanning still works and passive authentication is reported as unavailable.
- If you do provide one, it must be a PEM source from a local file URL or an HTTPS URL.
- The CSCA source can be a master list PEM, a single country-specific CSCA PEM, or a PEM bundle covering one or more countries.
- Card testing is still limited. Different passports, IDs, devices, and iOS versions may behave differently.
The basic host flow is:
- Collect MRZ input.
- Create
ScanConfiguration. - Call
RoxyNFCScanner.start(...). - Handle completion, failure, or cancellation in
RoxyNFCScannerDelegate.
Example:
import UIKit
import RoxyNFCScanner
final class ViewController: UIViewController, RoxyNFCScannerDelegate {
@objc
private func startNFCScan() {
do {
let mrzInput = try MRZKeyInput(
documentNumber: "X00000003",
dateOfBirth: "900103",
expiryDate: "300103"
)
let scanConfiguration = ScanConfiguration()
try RoxyNFCScanner.start(
mrzInput: mrzInput,
scanConfiguration: scanConfiguration,
delegate: self
)
} catch let error as ScannerError {
// Handle pre-start rejection or invalid input.
} catch {
// Handle non-scanner setup errors.
}
}
func didFailScan(event: ScanFailureEvent) {}
func didCompleteScan(result: DocumentDataResult) {}
func didCancelScan() {}
}To enable passive authentication for a scan, create the configuration with ScanConfiguration(cscaCertificateSourceURL: yourPEMURL).
RoxyNFCScanner.start(...) throws only before an attempt exists. After a scan starts, the package emits exactly one terminal delegate callback: completion, failure, or cancellation.
Use these types directly from the host app:
RoxyNFCScanner.start(mrzInput:scanConfiguration:delegate:)MRZKeyInputScanConfigurationRoxyNFCScannerDelegateScannerErrorScanFailureEventDocumentDataResult
DocumentDataResultcan still be successful even if some DGs are missing, unsupported, or failed.result.authenticationReport.passiveAuthentication.statuscan bepassed,failed, orunavailable.unavailablemeans passive authentication was not attempted, usually because no CSCA source was configured.result.authenticationReport.activeAuthentication.statuscan also be unavailable depending on chip support.
ScannerErrorfromstart(...)means the scan never started.didFailScan(event:),didCompleteScan(result:), anddidCancelScan()are mutually exclusive terminal callbacks.- Start rejection is reported as
ScannerError.startRejected(rejection:).
The reference host flow lives in Example/Example/ViewController.swift.