Claude
Skills
Sign in
Back

core-nfc

Included with Lifetime
$97 forever

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.

Writing & Docs

What this skill does


# CoreNFC

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. Targets Swift 6.3 / iOS 26+.

## Contents

- [Setup](#setup)
- [NDEF Reader Session](#ndef-reader-session)
- [Tag Reader Session](#tag-reader-session)
- [Writing NDEF Messages](#writing-ndef-messages)
- [NDEF Payload Types](#ndef-payload-types)
- [Background Tag Reading](#background-tag-reading)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)

## Setup

### Project Configuration

1. Add the **Near Field Communication Tag Reading** capability in Xcode
2. Add `NFCReaderUsageDescription` to Info.plist with a user-facing reason string
3. Add the `com.apple.developer.nfc.readersession.formats` entitlement with the current `TAG` value; do not add legacy `NDEF`
4. For ISO 7816 tags, add supported application identifiers to `com.apple.developer.nfc.readersession.iso7816.select-identifiers` in Info.plist
5. For FeliCa tags, add supported system codes to `com.apple.developer.nfc.readersession.felica.systemcodes`; do not use wildcard system codes

### Device Requirements

NFC 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.

```swift
import CoreNFC

guard NFCNDEFReaderSession.readingAvailable else {
    // Device does not support NFC or feature is restricted
    showUnsupportedMessage()
    return
}
```

### Key Types

| 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 |

## NDEF Reader Session

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.

```swift
import 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
    }
}
```

### Reading with Tag Connection

For read-write operations, use the tag-detection delegate method to connect
to individual tags:

```swift
func 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()
            }
        }
    }
}
```

## Tag Reader Session

Use `NFCTagReaderSession` when you need direct access to the native tag
protocol (ISO 7816, ISO 15693, FeliCa, or MIFARE).

Polling options are protocol-specific: `.iso14443` detects ISO
7816-compatible and MIFARE tags, `.iso15693` detects ISO 15693 tags, and
`.iso18092` detects FeliCa tags. Do not use `NFCTagReaderSession` for
payment-related AIDs; Apple documents `NFCPaymentTagReaderSession` for
eligible EU payment use cases.

```swift
final class TagReader: NSObject, NFCTagReaderSessionDelegate {
    private var session: NFCTagReaderSession?

    func beginScanning() {
        guard NFCTagReaderSession.readingAvailable else { return }

        session = NFCTagReaderSession(
            pollingOption: [.iso14443, .iso15693, .iso18092],
            delegate: self,
            queue: nil
        )
        session?.alertMessage = "Hold your iPhone near a tag."
        session?.begin()
    }

    func tagReaderSessionDidBecomeActive(
        _ session: NFCTagReaderSession
    ) { }

    func tagReaderSession(
        _ session: NFCTagReaderSession,
        didDetect tags: [NFCTag]
    ) {
        guard let tag = tags.first else { return }

        session.connect(to: tag) { error in
            guard error == nil else {
                session.invalidate(
                    errorMessage: "Connection failed."
                )
                return
            }

            switch tag {
            case .iso7816(let iso7816Tag):
                self.readISO7816(tag: iso7816Tag, session: session)
            case .miFare(let miFareTag):
                self.readMiFare(tag: miFareTag, session: session)
            case .iso15693(let iso15693Tag):
                self.readISO15693(tag: iso15693Tag, session: session)
            case .feliCa(let feliCaTag):
                self.readFeliCa(tag: feliCaTag, session: session)
            @unknown default:
                session.invalidate(errorMessage: "Unsupported tag type.")
            }
        }
    }

    func tagReaderSession(
        _ session: NFCTagReaderSession,
        didInvalidateWithError error: Error
    ) {
        self.session = nil
    }
}
```

## Writing NDEF Messages

Write NDEF data to a connected tag. Always check `readWrite` status first.

```swift
func 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)"
   

Related in Writing & Docs