NFC Integration in Mobile Applications
NFC in mobile is used for three tasks: reading tags (passports, transit cards, NFC tags), writing to rewritable tags, and peer-to-peer exchange with another device. Platforms implement these modes differently, and iOS historically restricted capabilities significantly more than Android.
iOS: Core NFC and Its Limitations
iOS supports NFC since iPhone 7 (iOS 11), but full NDEF and non-NDEF tag reading appeared only in iOS 13. Tag writing—iOS 13+. Background tag reading (without explicitly starting a session)—iOS 14+.
Key classes: NFCNDEFReaderSession for NDEF tags, NFCTagReaderSession for non-NDEF (ISO 7816, ISO 15693, FeliCa, MIFARE).
import CoreNFC
class NFCManager: NSObject, NFCNDEFReaderSessionDelegate {
var session: NFCNDEFReaderSession?
func startReading() {
guard NFCNDEFReaderSession.readingAvailable else {
// device doesn't support NFC or NFC is off
return
}
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = "Hold NFC tag"
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession,
didDetectNDEFs messages: [NFCNDEFMessage]) {
for message in messages {
for record in message.records {
if record.typeNameFormat == .nfcWellKnown,
let type = String(data: record.type, encoding: .utf8),
type == "T" {
// text record
let payload = record.payload
// first byte—encoding + lang length, parse manually
}
}
}
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
// NFCReaderError.readerSessionInvalidationErrorUserCanceled — user closed
// NFCReaderError.readerSessionInvalidationErrorSessionTimeout — timeout
}
}
Writing to Tags
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
guard let tag = tags.first else { return }
session.connect(to: tag) { error in
guard error == nil else { return }
tag.queryNDEFStatus { status, capacity, error in
guard status == .readWrite else {
session.invalidate(errorMessage: "Tag is write-protected")
return
}
let payload = NFCNDEFPayload.wellKnownTypeURIPayload(url: URL(string: "https://example.com")!)!
let message = NFCNDEFMessage(records: [payload])
tag.writeNDEF(message) { error in
session.invalidate(errorMessage: error != nil ? "Write error" : nil)
}
}
}
}
For non-NDEF tags (bank cards ISO 7816, passports), use NFCTagReaderSession with pollingOption: [.iso14443]. Reading passports (ePassport)—through PACE or Basic Access Control with keys from the MRZ line of the document.
Android: NfcAdapter and Foreground Dispatch
Android NFC API is more open. NfcAdapter.getDefaultAdapter(context) checks for NFC presence. enableForegroundDispatch intercepts tags while the app is in foreground.
val nfcAdapter = NfcAdapter.getDefaultAdapter(context)
// In onResume
val intent = Intent(context, activity.javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE)
nfcAdapter.enableForegroundDispatch(activity, pendingIntent, null, null)
// In onNewIntent
fun handleNfcIntent(intent: Intent) {
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG) ?: return
val ndef = Ndef.get(tag)
if (ndef != null) {
ndef.connect()
val message = ndef.ndefMessage
for (record in message.records) {
val payload = String(record.payload, Charsets.UTF_8)
}
ndef.close()
}
}
For MIFARE Classic—separate class MifareClassic, requires authentication with key before sector reading. Default key A: {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}. Most transit cards use custom keys.
Typical Issues
iOS session timeout. NFCNDEFReaderSession lives 60 seconds. If the user doesn't hold the tag in time—session closes with error. Show clear instructions and offer to retry.
Conflict with payment apps on Android. If Google Pay is installed and set as default NFC app, enableForegroundDispatch intercepts tags only while the app is active. In background—Google Pay controls.
NFC doesn't work in case. Not a joke—some metal cases shield the antenna. This must be described in usage requirements.
Integration timeline: 2-3 days—NDEF reading; 4-7 days—if writing, non-NDEF, or work with protected tags is needed. Cost is calculated individually.







