Scan and Dump
Use the top-level API when the app owns the NFC session. Keep the returned session alive while sending commands through the transport.
Scan
do {
let (info, transport, session) = try await CoreExtendedNFC.scan()
defer { session.invalidate() }
// ...
} catch where error.isNFCCancellation {
// The user dismissed the sheet (NFCError.userCanceled) or the task was cancelled.
}
When the user dismisses the NFC sheet, scan() throws NFCError.userCanceled, and later failures on that session's transport are reported the same way. Only one scan may be pending per NFCSessionManager; another call throws NFCError.scanInProgress. See Errors.
Polling Targets and Session Managers
scan and scanAndDump poll for every supported technology by default. Pass pollingTargets to narrow it, so other tag types in the field are not detected:
let (info, transport, session) = try await CoreExtendedNFC.scan(pollingTargets: [.iso15693])
Both also take a manager: argument. Pass your own NFCSessionManager, or a subclass that overrides hooks such as sessionDidBecomeActive(_:), sessionDidInvalidate(_:error:) or identifyTag(_:monitor:). scan returns the instance you passed; scanAndDump invalidates it before returning.
let manager = AppSessionManager() // your NFCSessionManager subclass
let (info, dump) = try await CoreExtendedNFC.scanAndDump(manager: manager)
See Subclassing for the hooks and the thread-safety contract.
Identification Refinement
scan() first maps the CoreNFC tag family into a coarse CardType, then refines that result with family-specific probes where iOS exposes safe commands.
For MIFARE Type 2 tags, CoreNFC may only expose an unknown MIFARE family. If the coarse result looks like MIFARE Classic, CoreExtendedNFC validates the Type 2 manufacturer pages with READ 0x30 0x00 and the UID BCC bytes before retrying the Ultralight / NTAG GET_VERSION path. Real MIFARE Classic cards remain identification-only because iOS does not expose Crypto1 authentication.
Other refinements: Ultralight C is recognized by a 16-byte answer to READ 0x2B (no authentication is attempted; an Ultralight C with page 2B read-protected stays generic Ultralight). NTAG210 and NTAG212 are recognized from GET_VERSION. NTAG I2C (GET_VERSION product subtype 05h) shares storage-size codes with NTAG216 but not its memory map, so it stays generic Ultralight instead of being reported as NTAG216. Likewise Ultralight Nano and NTAG 223 DNA, whose GET_VERSION major version is not 01h, stay generic Ultralight; NTAG210u and NTAG213 TT get the NTAG210 / NTAG213 maps. readUltralightCAccessConfiguration decodes Ultralight C AUTH0 / AUTH1 per MF0ICU2 §7.5.8: AUTH0 30h or higher disables protection, and AUTH1 bit 0 = 1 restricts only writes (reads stay free) while 0 restricts reads and writes. FeliCa system code 88B4 is split into Lite and Lite-S by reading block 0x90, which only Lite-S has. ATQA 0304 with SAK 20 is DESFire with a random UID. Other SAK 20 (including MIFARE Plus SL3, which AN10833 §2.1 says is told apart by its ATS, not ATQA / SAK), 28 and 38 cards start as generic ISO 7816 (.smartMX) and are probed for DESFire, Type 4 and eMRTD.
Dump
let (info, dump) = try await CoreExtendedNFC.scanAndDump()
print(dump.summary.userSummary)
Some dumps are partial by design. Check dump.capabilities for .partiallyReadable and read dump.facts:
- Ultralight / NTAG: reading stops at the first page the tag refuses (AUTH0 read protection). Pages read so far are kept, an
Unauthenticated Boundaryfact names the page, and capabilities becomeauthenticationRequired+partiallyReadable. Ultralight C key pages 44-47 are never read. - ISO 15693: the memory size comes from GET SYSTEM INFORMATION, and from EXTENDED GET SYSTEM INFORMATION (3Bh) when the former reports 256 blocks (its one-byte maximum) or no size, so a 2048-block ST25DV64K is dumped in full. Blocks 0-255 use READ SINGLE BLOCK; blocks from 256 on use EXTENDED READ SINGLE BLOCK. If the tag rejects the extended command, the dump stops at block 255, a
Blocks Readfact such as256 of 2048is added, and the dump is markedpartiallyReadable. Tags that do not report their memory size throwunsupportedOperation. - DESFire: free-access files only, keyed by AID + file ID. Protected applications and files are skipped and counted.
- FeliCa: the Type 3 NDEF area plus up to 4 blocks per discovered plain service. Type 4: the CC and NDEF files.
Cancelling the task stops a dump between pages, blocks or files with CancellationError.
Export
let json = try CoreExtendedNFC.exportJSON(dump: dump)
let flipper = try CoreExtendedNFC.exportFlipperNFC(dump: dump)
let artifacts = try CoreExtendedNFC.exportAll(dump: dump)
The Flipper, Proxmark3 MFU and libnfc MFD formats store exactly 4 bytes per Type 2 page. Their exports throw NFCError.malformedDump when any page has another length (for example a hand-edited or decoded dump) rather than padding or truncating it.