Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Read and write NFC tags using CoreNFC. Use when scanning NDEF tags, reading ISO7816/ISO15693/FeliCa/MIFARE tags, writing NDEF messages, handling NFC session lifecycle, configuring NFC entitlements, or implementing background tag reading in iOS apps.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 125% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 131% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 141% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-22 | ✓→✓ | = Same ✓ | 93% | 0% |
Read and write NFC tags on iPhone using the CoreNFC framework. Covers NDEF reader sessions, tag reader sessions, NDEF message construction, entitlements, and background tag reading.
NFCReaderUsageDescription to Info.plist with a user-facing reason stringcom.apple.developer.nfc.readersession.formats entitlement with the current TAG value; do not add legacy NDEFcom.apple.developer.nfc.readersession.iso7816.select-identifiers in Info.plistcom.apple.developer.nfc.readersession.felica.systemcodes; do not use wildcard system codesNFC reading requires iPhone 7 or later. Always check for reader session availability before creating NFC UI or sessions. Use the concrete reader session type you are about to create.
swiftimport CoreNFC guard NFCNDEFReaderSession.readingAvailable else { // Device does not support NFC or feature is restricted showUnsupportedMessage() return }
| Type | Role | |---|---| | NFCNDEFReaderSession | Scans for NDEF-formatted tags | | NFCTagReaderSession | Scans for ISO7816, ISO15693, FeliCa, MIFARE tags | | NFCNDEFMessage | Collection of NDEF payload records | | NFCNDEFPayload | Single record within an NDEF message | | NFCNDEFTag | Protocol for interacting with an NDEF-capable tag |
Use NFCNDEFReaderSession to read NDEF-formatted data from tags. This is the simplest path for reading standard tag content like URLs, text, and MIME data.
swiftimport CoreNFC final class NDEFReader: NSObject, NFCNDEFReaderSessionDelegate { private var session: NFCNDEFReaderSession? func beginScanning() { guard NFCNDEFReaderSession.readingAvailable else { return } session = NFCNDEFReaderSession( delegate: self, queue: nil, invalidateAfterFirstRead: false ) session?.alertMessage = "Hold your iPhone near an NFC tag." session?.begin() } // MARK: - NFCNDEFReaderSessionDelegate func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) { // Session is scanning } func readerSession( _ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage] ) { for message in messages { for record in message.records { processRecord(record) } } } func readerSession( _ session: NFCNDEFReaderSession, didInvalidateWithError error: Error ) { let nfcError = error as? NFCReaderError if nfcError?.code != .readerSessionInvalidationErrorFirstNDEFTagRead, nfcError?.code != .readerSessionInvalidationErrorUserCanceled { print("Session invalidated: \(error.localizedDescription)") } self.session = nil } }
For read-write operations, use the tag-detection delegate method to connect to individual tags:
swiftfunc readerSession( _ session: NFCNDEFReaderSession, didDetect tags: [any NFCNDEFTag] ) { guard let tag = tags.first else { session.restartPolling() return } session.connect(to: tag) { error in if let error { session.invalidate(errorMessage: "Connection failed: \(error)") return } tag.queryNDEFStatus { status, capacity, error in guard error == nil else { session.invalidate(errorMessage: "Query failed.") return } switch status { case .notSupported: session.invalidate(errorMessage: "Tag is not NDEF compliant.") case .readOnly: tag.readNDEF { message, error in if let message { self.processMessage(message) } session.invalidate() } case .readWrite: tag.readNDEF { message, error in if let message { self.processMessage(message) } session.alertMessage = "Tag read successfully." session.invalidate() } @unknown default: session.invalidate() } } } }
Use NFCTagReaderSession when you need direct access to the native tag protocol (ISO 7816, ISO 15693, FeliCa, or MIFARE).
| Polling option | Tags | |---|---| | .iso14443 | ISO 7816-compatible and MIFARE | | .iso15693 | ISO 15693 | | .iso18092 | FeliCa |
Do not use this session for payment-related AIDs. Load nfc-patterns.md for protocol-specific connection, APDU, command, and response handling.
Write NDEF data to a connected tag. Always check readWrite status first.
swiftfunc writeToTag( tag: any NFCNDEFTag, session: NFCNDEFReaderSession, url: URL ) { tag.queryNDEFStatus { status, capacity, error in guard status == .readWrite else { session.invalidate(errorMessage: "Tag is read-only.") return } guard let payload = NFCNDEFPayload.wellKnownTypeURIPayload( url: url ) else { session.invalidate(errorMessage: "Invalid URL.") return } let message = NFCNDEFMessage(records: [payload]) tag.writeNDEF(message) { error in if let error { session.invalidate( errorMessage: "Write failed: \(error.localizedDescription)" ) } else { session.alertMessage = "Tag written successfully." session.invalidate() } } } }
swift// URL payload let urlPayload = NFCNDEFPayload.wellKnownTypeURIPayload( url: URL(string: "https://example.com")! ) // Text payload let textPayload = NFCNDEFPayload.wellKnownTypeTextPayload( string: "Hello NFC", locale: Locale(identifier: "en") ) // Custom payload let customPayload = NFCNDEFPayload( format: .nfcExternal, type: "com.example:mytype".data(using: .utf8)!, identifier: Data(), payload: "custom-data".data(using: .utf8)! )
Load Parsing NDEF Payload Content for the complete type-name-format switch and multi-record handling.
On iPhone XS and later, iOS can read NFC tags in the background without opening your app. The NDEF message must contain a URI record (typeNameFormat == .nfcWellKnown, type U). If there are multiple URI records, the system uses the first one.
For app-specific routing, write a universal link to the tag and configure the Associated Domains capability for that domain. Background tag reading also supports specific system URL schemes such as web, email, SMS, telephone, FaceTime, Maps, and HomeKit setup. It does not support custom URL schemes, and the system does not route by bundle ID or arbitrary NDEF content type.
When a user taps a compatible tag, iOS displays a notification that opens your app. Handle the tag data via NSUserActivity:
swiftfunc scene( _ scene: UIScene, continue userActivity: NSUserActivity ) { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb else { return } let message = userActivity.ndefMessagePayload guard message.records.first?.typeNameFormat != .empty else { return } for record in message.records { processRecord(record) } }
Without the com.apple.developer.nfc.readersession.formats entitlement, reader sessions cannot access NFC hardware. Use the current TAG value for Core NFC reader sessions; do not copy older examples that add NDEF.
The session invalidates for multiple reasons. Distinguishing user cancellation from real errors prevents false error alerts.
swift// WRONG -- shows error when user cancels func readerSession( _ session: NFCNDEFReaderSession, didInvalidateWithError error: Error ) { showAlert("NFC Error: \(error.localizedDescription)") } // CORRECT -- filter expected invalidation reasons func readerSession( _ session: NFCNDEFReaderSession, didInvalidateWithError error: Error ) { let nfcError = error as? NFCReaderError switch nfcError?.code { case .readerSessionInvalidationErrorUserCanceled, .readerSessionInvalidationErrorFirstNDEFTagRead: break // Normal termination default: showAlert("NFC Error: \(error.localizedDescription)") } self.session = nil }
Once a session is invalidated, it cannot be restarted. Nil out your reference and create a new session for the next scan.
swift// WRONG -- reusing invalidated session func scanAgain() { session?.begin() // Does nothing, session is dead } // CORRECT -- create a new session func scanAgain() { session = NFCNDEFReaderSession( delegate: self, queue: nil, invalidateAfterFirstRead: false ) session?.begin() }
NFCReaderUsageDescription set in Info.plistcom.apple.developer.nfc.readersession.formats entitlement uses TAG, not legacy NDEFNFCNDEFReaderSession.readingAvailable or NFCTagReaderSession.readingAvailable checked before creating sessionsbegin()didInvalidateWithError distinguishes user cancellation from actual errorsNFCTagReaderSession.iso18092NFCTagReaderSessionOther measured skills in the registry, with their headline benchmark lift.