iOS SDK Integration
Integration guide for the CICReader iOS SDK, version 1.0.7. Written in Swift; the entry point is CESNfc.
Build Specifications
| What | Description |
|---|---|
| Xcode | 16.0+ |
| Target iOS version | 13+ |
Integration
Add the SDK framework to your target, then these pods:
pod 'OpenSSL-Universal', '~> 1.1.180'
pod 'PromisesSwift', '~> 2.4.0'Enable the Near Field Communication Tag Reading capability, then declare the usage description and the ISO7816 application identifiers:
<key>NFCReaderUsageDescription</key>
<string>We would like to use NFC to scan NID chip</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A0000002471001</string>
<string>A0000002472001</string>
<string>00000000000000</string>
</array>WARNING
Missing any of the three ISO7816 identifiers makes the chip read fail silently in production builds while working in debug. See Verifying the IPA Configuration below.
Initialize
let nfcConfig = CESNfcConfig(baseUrl: "<BASE_URL>",
enableCheckNfcData: true,
readTimeout: 60000,
writeTimeout: 60000)
let nfcReader = CESNfc(nfcConfig)
nfcReader.delegate = self // optional
nfcReader.displayMessageDelegate = self // optionalParameters:
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
baseUrl | string | Yes | — | Base URL for API calls. |
enableCheckNfcData | bool | No | true | Verify NFC data against Cake RAR. |
readTimeout | int | No | 60000 | API read timeout in milliseconds. |
writeTimeout | int | No | 60000 | API write timeout in milliseconds. |
cacheScanStrategy | enum | No | .NO_CACHE | See Mobile SDK Overview § Cache on Server Errors. |
sessionDelayConfig | CESSessionDelayConfig | No | — | Per-device retry delays. See below. |
Custom headers
extension DemoViewController: CESNfcDelegate {
func provideHeaders() -> [String: String?] {
return [
"custom_header1": "value1",
"custom_header2": "value2"
]
}
}Custom display messages
Every method is optional — override only the prompts you want to change.
extension ViewController: CESNfcDisplayMessageDelegate {
func presentRequestPresentPassport() -> String { }
func presentAuthenticatingWithPassport(progress: Int) -> String { }
func presentReadingDataGroupProgress(dataGroup: DataGroupId, progress: Int) -> String { }
func presentError(error: CESNfcError) -> String { }
func presentSuccessfulRead() -> String { }
func presentStartVerifyData() -> String { }
func presentVerifyDataDone() -> String { }
}Read the Chip
Using MRZ — 9-digit ID, date of birth, expiry date
Throws InvalidFullMRZKey when any input is wrong.
nfcReader.readNfc(
idCard: "xxxxxxxxx",
dayOfBirth: "901212",
expireDate: "301212",
accessToken: "<ACCESS_TOKEN>",
refId: "ref_00000001")
.then { result in
// result
}.catch { err in
// handle error
}| Param | Type | Required | Default | Description |
|---|---|---|---|---|
idCard | string | Yes | — | The last 9 digits of the NID number. |
dayOfBirth | string | Yes | — | Date of birth, format yyMMdd. |
expireDate | string | Yes | — | Card expiry date, format yyMMdd. |
accessToken | string | Yes | — | Request access token. |
refId | string | Yes | — | Transaction reference ID to verify. |
Using CAN — 12-digit ID number
Throws InvalidCANKey when the number is wrong.
nfcReader.readNfc(
docId: "001xxxxxxxxx",
accessToken: "<ACCESS_TOKEN>",
refId: "ref_00000001")
.then { result in
// result
}.catch { err in
// handle error
}| Param | Type | Required | Default | Description |
|---|---|---|---|---|
docId | string | Yes | — | The full 12-digit NID number. |
accessToken | string | Yes | — | Request access token. |
refId | string | Yes | — | Transaction reference ID to verify. |
Result
CESNfcResult carries the raw data groups plus, when enableCheckNfcData is true, the parsed citizen fields.
Chip data
| Field | Type | Description |
|---|---|---|
com | string | Unicode version info and the list of available data groups. Base64. |
sod | string | The SOD data from the NFC scan. Base64. |
dg1 | string | Individual basic details — MRZ information. Base64. |
dg2 | string | Citizen facial image encoding. Base64. |
dg13 | string | Citizen-related information. Base64. |
dg14 | string | Used for the EAC access mechanism. Base64. |
dg15 | string | Public key, used for Passive Authentication. Base64. |
cloneStatus | CESNfcVerdict | Clone status of the card. See Mobile SDK Overview § Clone Status. |
imageData | String? | Computed facial image contained in the dg2 data. |
Citizen fields — API derived
| Field | Type | Description |
|---|---|---|
idCardNo | string | Identification number of the citizen. |
oldIdCardNumber | string | Old identification number. |
fullName | string | Full name. |
dateOfBirth | string | Date of birth. |
dateOfExpiry | string | Card expiry date. |
gender | string | Gender. |
nationality | string | Nationality. |
ethnic | string | Ethnicity. |
religion | string | Religion. |
placeOfOrigin | string | Place of origin. |
residenceAddress | string | Place of residence. |
personalSpecificIdentification | string | Personal identification characteristics. |
dateOfIssuance | string | Date the card was issued. |
fatherName | string | Father's name. |
motherName | string | Mother's name. |
spouseName | string | Partner's name. |
faceImage | string | Facial image. Base64. |
isVerified | bool | Whether the card was verified by RAR. |
verificationKey | string | Verification key — pass this to Verify NFC. |
requestId | string | Request ID of the authentication. |
iOS uses idCardNo and residenceAddress where Flutter uses citizenIdentify and placeOfResidence — same values, different names.
Event Listener
// nfcReader.eventDelegate = self
public func nfcScanWillStart() -> Void { }
public func nfcScanDidEnd(isScanSuccess: Bool) -> Void { }
public func dataVerificationWillStart(isCacheHit: Bool) -> Void { }
public func dataVerificationDidEnd(isCacheHit: Bool) -> Void { }When caching is enabled and the cache hits, nfcScanDidEnd never fires — render your own verification UI from dataVerificationWillStart:
public func dataVerificationWillStart(isCacheHit: Bool) -> Void {
if (isCacheHit) {
// show your UI for data verification here
} else {
// SDK shows its dialog and asks the user to scan as normal
}
}
public func dataVerificationDidEnd(isCacheHit: Bool) -> Void {
if (isCacheHit) {
// close your UI for data verification here
} else {
// SDK closes its own dialog
}
}Retry Delays
Each CESNfcError.SessionOnGoing comes with a recommended wait before the next scan attempt. The SDK handles this automatically; you can tune the initial duration per device model.
CESNfcConfig(baseUrl: "<BASE_URL>",
sessionDelayConfig: CESSessionDelayConfig(
defaultDelayMs: 10000,
mappingDurations: [
DeviceModel.iPhone15ProMax.rawValue: 15000,
DeviceModel.iPhone7Plus.rawValue: 12000
])
)Error Handling
Errors are thrown as CESNfcError. The full list is in Error Codes; the ones you will hit most:
| Field | Type | Description |
|---|---|---|
NFCNotSupported | 600032 | NFC is not supported on this device. |
SessionOnGoing | 600101 | Another reading session is in progress. |
SystemResourceUnavailable | 600102 | The NFC system is busy. |
UserCanceled | 600432 | The user cancelled. |
InvalidFullMRZKey | 600316 | The MRZ key is invalid. |
InvalidCANKey | 600317 | The CAN key is invalid. |
InvalidCardType | 600396 | The scanned card is not an NID. |
NetworkError | 601400 | A network error occurred. |
ServerError | 601503 | A server error occurred. |
Built-in Functions
public var isNfcAvailable: Bool {
NFCNDEFReaderSession.readingAvailable
}iOS has no isNfcEnable or openNfcSetting — those are Android-only.
Verifying the IPA Configuration
If NFC works in debug but fails in a production build, check the built artefact.
Info.plist must contain:
NFCReaderUsageDescriptionwith a usage descriptioncom.apple.developer.nfc.readersession.iso7816.select-identifierscontainingA0000002471001,A0000002472001and00000000000000
embedded.mobileprovision must contain com.apple.developer.nfc.readersession.formats with the TAG entitlement.
Next Steps
- Verify NFC — redeem the
verificationKeyfrom your backend - Error Codes — the full iOS error table
- Mobile SDK Overview — concepts shared across platforms