core-nfc
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCoreNFC
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.2 / iOS 26+.
使用CoreNFC框架在iPhone上读写NFC标签。内容涵盖NDEF读取会话、标签读取会话、NDEF消息构建、权限配置以及后台标签读取。适配Swift 6.2 / iOS 26+。
Contents
目录
Setup
配置
Project Configuration
项目配置
- Add the Near Field Communication Tag Reading capability in Xcode
- Add to Info.plist with a user-facing reason string
NFCReaderUsageDescription - Add the entitlement with the tag types your app reads (e.g.,
com.apple.developer.nfc.readersession.formats,NDEF)TAG - For ISO 7816 tags, add supported application identifiers to in Info.plist
com.apple.developer.nfc.readersession.iso7816.select-identifiers
- 在Xcode中添加Near Field Communication Tag Reading能力
- 在Info.plist中添加字段,并填写面向用户的权限说明字符串
NFCReaderUsageDescription - 添加权限,并配置应用支持的标签类型(例如:
com.apple.developer.nfc.readersession.formats,NDEF)TAG - 若使用ISO 7816标签,需在Info.plist的中添加支持的应用标识符
com.apple.developer.nfc.readersession.iso7816.select-identifiers
Device Requirements
设备要求
NFC reading requires iPhone 7 or later. Always check for reader session
availability before presenting NFC UI.
swift
import CoreNFC
guard NFCNDEFReaderSession.readingAvailable else {
// Device does not support NFC or feature is restricted
showUnsupportedMessage()
return
}NFC读取功能需要iPhone 7或更高版本。在展示NFC界面前,务必检查读取会话是否可用。
swift
import CoreNFC
guard NFCNDEFReaderSession.readingAvailable else {
// 设备不支持NFC或该功能被限制
showUnsupportedMessage()
return
}Key Types
核心类型
| Type | Role |
|---|---|
| Scans for NDEF-formatted tags |
| Scans for ISO7816, ISO15693, FeliCa, MIFARE tags |
| Collection of NDEF payload records |
| Single record within an NDEF message |
| Protocol for interacting with an NDEF-capable tag |
| 类型 | 作用 |
|---|---|
| 扫描NDEF格式的标签 |
| 扫描ISO7816、ISO15693、FeliCa、MIFARE标签 |
| NDEF载荷记录的集合 |
| NDEF消息中的单个记录 |
| 与支持NDEF的标签交互的协议 |
NDEF Reader Session
NDEF读取会话
Use to read NDEF-formatted data from tags. This is the
simplest path for reading standard tag content like URLs, text, and MIME data.
NFCNDEFReaderSessionswift
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
}
}使用读取标签中的NDEF格式数据。这是读取URL、文本、MIME数据等标准标签内容的最简方式。
NFCNDEFReaderSessionswift
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 = "将iPhone靠近NFC标签。"
session?.begin()
}
// MARK: - NFCNDEFReaderSessionDelegate
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
// 会话正在扫描
}
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("会话失效:\(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()
}
}
}
}对于读写操作,使用标签检测代理方法连接到单个标签:
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: "连接失败:\(error)")
return
}
tag.queryNDEFStatus { status, capacity, error in
guard error == nil else {
session.invalidate(errorMessage: "查询失败。")
return
}
switch status {
case .notSupported:
session.invalidate(errorMessage: "标签不兼容NDEF。")
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 = "标签读取成功。"
session.invalidate()
}
@unknown default:
session.invalidate()
}
}
}
}Tag Reader Session
标签读取会话
Use when you need direct access to the native tag
protocol (ISO 7816, ISO 15693, FeliCa, or MIFARE).
NFCTagReaderSessionswift
final class TagReader: NSObject, NFCTagReaderSessionDelegate {
private var session: NFCTagReaderSession?
func beginScanning() {
session = NFCTagReaderSession(
pollingOption: [.iso14443, .iso15693],
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
}
}当需要直接访问原生标签协议(ISO 7816、ISO 15693、FeliCa或MIFARE)时,使用。
NFCTagReaderSessionswift
final class TagReader: NSObject, NFCTagReaderSessionDelegate {
private var session: NFCTagReaderSession?
func beginScanning() {
session = NFCTagReaderSession(
pollingOption: [.iso14443, .iso15693],
delegate: self,
queue: nil
)
session?.alertMessage = "将iPhone靠近标签。"
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: "连接失败。"
)
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: "不支持的标签类型。")
}
}
}
func tagReaderSession(
_ session: NFCTagReaderSession,
didInvalidateWithError error: Error
) {
self.session = nil
}
}Writing NDEF Messages
写入NDEF消息
Write NDEF data to a connected tag. Always check status first.
readWriteswift
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)"
)
} else {
session.alertMessage = "Tag written successfully."
session.invalidate()
}
}
}
}向已连接的标签写入NDEF数据。务必先检查标签的状态。
readWriteswift
func writeToTag(
tag: any NFCNDEFTag,
session: NFCNDEFReaderSession,
url: URL
) {
tag.queryNDEFStatus { status, capacity, error in
guard status == .readWrite else {
session.invalidate(errorMessage: "标签为只读。")
return
}
guard let payload = NFCNDEFPayload.wellKnownTypeURIPayload(
url: url
) else {
session.invalidate(errorMessage: "无效URL。")
return
}
let message = NFCNDEFMessage(records: [payload])
tag.writeNDEF(message) { error in
if let error {
session.invalidate(
errorMessage: "写入失败:\(error.localizedDescription)"
)
} else {
session.alertMessage = "标签写入成功。"
session.invalidate()
}
}
}
}NDEF Payload Types
NDEF载荷类型
Creating Common Payloads
创建常见载荷
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)!
)swift
// URL载荷
let urlPayload = NFCNDEFPayload.wellKnownTypeURIPayload(
url: URL(string: "https://example.com")!
)
// 文本载荷
let textPayload = NFCNDEFPayload.wellKnownTypeTextPayload(
string: "Hello NFC",
locale: Locale(identifier: "en")
)
// 自定义载荷
let customPayload = NFCNDEFPayload(
format: .nfcExternal,
type: "com.example:mytype".data(using: .utf8)!,
identifier: Data(),
payload: "custom-data".data(using: .utf8)!
)Parsing Payload Content
解析载荷内容
swift
func processRecord(_ record: NFCNDEFPayload) {
switch record.typeNameFormat {
case .nfcWellKnown:
if let url = record.wellKnownTypeURIPayload() {
print("URL: \(url)")
} else if let (text, locale) = record.wellKnownTypeTextPayload() {
print("Text (\(locale)): \(text)")
}
case .absoluteURI:
if let uri = String(data: record.payload, encoding: .utf8) {
print("Absolute URI: \(uri)")
}
case .media:
let mimeType = String(data: record.type, encoding: .utf8) ?? ""
print("MIME type: \(mimeType), size: \(record.payload.count)")
case .nfcExternal:
let type = String(data: record.type, encoding: .utf8) ?? ""
print("External type: \(type)")
case .empty, .unknown, .unchanged:
break
@unknown default:
break
}
}swift
func processRecord(_ record: NFCNDEFPayload) {
switch record.typeNameFormat {
case .nfcWellKnown:
if let url = record.wellKnownTypeURIPayload() {
print("URL: \(url)")
} else if let (text, locale) = record.wellKnownTypeTextPayload() {
print("文本(\(locale)): \(text)")
}
case .absoluteURI:
if let uri = String(data: record.payload, encoding: .utf8) {
print("绝对URI: \(uri)")
}
case .media:
let mimeType = String(data: record.type, encoding: .utf8) ?? ""
print("MIME类型: \(mimeType), 大小: \(record.payload.count)")
case .nfcExternal:
let type = String(data: record.type, encoding: .utf8) ?? ""
print("外部类型: \(type)")
case .empty, .unknown, .unchanged:
break
@unknown default:
break
}
}Background Tag Reading
后台标签读取
On iPhone XS and later, iOS can read NFC tags in the background without
opening your app. To opt in:
- Add associated domains or universal links that match the URL on your tags
- Register your app for the tag's NDEF content type
- Include your app's bundle ID in the tag's NDEF record
When a user taps a compatible tag, iOS displays a notification that opens
your app. Handle the tag data via :
NSUserActivityswift
func scene(
_ scene: UIScene,
continue userActivity: NSUserActivity
) {
guard userActivity.activityType ==
NSUserActivityTypeBrowsingWeb else { return }
if let message = userActivity.ndefMessagePayload {
for record in message.records {
processRecord(record)
}
}
}在iPhone XS及更高版本上,iOS可以在后台读取NFC标签,无需打开应用。如需启用该功能:
- 添加与标签上URL匹配的关联域名或通用链接
- 为标签的NDEF内容类型注册应用
- 在标签的NDEF记录中包含应用的Bundle ID
当用户点击兼容的标签时,iOS会显示通知并打开应用。通过处理标签数据:
NSUserActivityswift
func scene(
_ scene: UIScene,
continue userActivity: NSUserActivity
) {
guard userActivity.activityType ==
NSUserActivityTypeBrowsingWeb else { return }
if let message = userActivity.ndefMessagePayload {
for record in message.records {
processRecord(record)
}
}
}Common Mistakes
常见错误
DON'T: Forget the NFC entitlement
错误做法:忘记添加NFC权限
Without the entitlement,
session creation crashes at runtime.
com.apple.developer.nfc.readersession.formatsswift
// WRONG -- entitlement not added, crashes
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)
// CORRECT -- add entitlement in Signing & Capabilities first
// Then the same code works:
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)如果没有添加权限,会话创建会在运行时崩溃。
com.apple.developer.nfc.readersession.formatsswift
// 错误示例 -- 未添加权限,会崩溃
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)
// 正确示例 -- 先在Signing & Capabilities中添加权限
// 之后相同代码即可正常运行:
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)DON'T: Skip the readingAvailable check
错误做法:跳过读取可用性检查
Attempting to create an NFC session on an unsupported device (iPad, iPod
touch, or iPhone 6s and earlier) crashes.
swift
// WRONG
func scan() {
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)
session.begin()
}
// CORRECT
func scan() {
guard NFCNDEFReaderSession.readingAvailable else {
showUnsupportedAlert()
return
}
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)
session.begin()
}在不支持的设备(iPad、iPod touch或iPhone 6s及更早机型)上尝试创建NFC会话会崩溃。
swift
// 错误示例
func scan() {
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)
session.begin()
}
// 正确示例
func scan() {
guard NFCNDEFReaderSession.readingAvailable else {
showUnsupportedAlert()
return
}
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)
session.begin()
}DON'T: Ignore session invalidation errors
错误做法:忽略会话失效错误
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
}会话会因多种原因失效。区分用户取消与实际错误可避免错误的提示信息。
swift
// 错误示例 -- 用户取消时仍显示错误
func readerSession(
_ session: NFCNDEFReaderSession,
didInvalidateWithError error: Error
) {
showAlert("NFC错误:\(error.localizedDescription)")
}
// 正确示例 -- 过滤预期的失效原因
func readerSession(
_ session: NFCNDEFReaderSession,
didInvalidateWithError error: Error
) {
let nfcError = error as? NFCReaderError
switch nfcError?.code {
case .readerSessionInvalidationErrorUserCanceled,
.readerSessionInvalidationErrorFirstNDEFTagRead:
break // 正常终止
default:
showAlert("NFC错误:\(error.localizedDescription)")
}
self.session = nil
}DON'T: Hold a strong reference to a stale session
错误做法:持有失效会话的强引用
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()
}会话失效后无法重启。应将引用置空,下次扫描时创建新会话。
swift
// 错误示例 -- 复用失效会话
func scanAgain() {
session?.begin() // 无作用,会话已失效
}
// 正确示例 -- 创建新会话
func scanAgain() {
session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: false
)
session?.begin()
}DON'T: Write without checking tag status
错误做法:未检查标签状态就写入
Writing to a read-only tag silently fails or produces confusing errors.
swift
// WRONG -- writes without checking status
tag.writeNDEF(message) { error in
// May fail on read-only tags
}
// CORRECT -- check status first
tag.queryNDEFStatus { status, capacity, error in
guard status == .readWrite else {
session.invalidate(errorMessage: "Tag is read-only.")
return
}
tag.writeNDEF(message) { error in
// Handle result
}
}向只读标签写入会静默失败或产生混淆的错误。
swift
// 错误示例 -- 未检查状态就写入
tag.writeNDEF(message) { error in
// 可能在只读标签上失败
}
// 正确示例 -- 先检查状态
tag.queryNDEFStatus { status, capacity, error in
guard status == .readWrite else {
session.invalidate(errorMessage: "标签为只读。")
return
}
tag.writeNDEF(message) { error in
// 处理结果
}
}Review Checklist
检查清单
- NFC capability added in Signing & Capabilities
- set in Info.plist
NFCReaderUsageDescription - entitlement configured with correct tag types
com.apple.developer.nfc.readersession.formats - checked before creating sessions
NFCNDEFReaderSession.readingAvailable - Session delegate set before calling
begin() - Session reference set to nil after invalidation
- distinguishes user cancellation from actual errors
didInvalidateWithError - NDEF status queried before write operations
- Tag capacity checked before writing large messages
- ISO 7816 application identifiers listed in Info.plist if using
NFCTagReaderSession - Background tag reading configured with associated domains if needed
- Only one reader session active at a time
- 在Signing & Capabilities中添加了NFC能力
- 在Info.plist中设置了
NFCReaderUsageDescription - 配置了权限,并指定了正确的标签类型
com.apple.developer.nfc.readersession.formats - 创建会话前检查了
NFCNDEFReaderSession.readingAvailable - 调用前设置了会话代理
begin() - 会话失效后将引用置空
- 区分了用户取消与实际错误
didInvalidateWithError - 写入操作前查询了NDEF状态
- 写入大消息前检查了标签容量
- 若使用,在Info.plist中列出了ISO 7816应用标识符
NFCTagReaderSession - 如需后台标签读取,已配置关联域名
- 同一时间仅存在一个活跃的读取会话
References
参考资料
- Extended patterns (ISO 7816 commands, multi-tag scanning, NDEF locking):
references/nfc-patterns.md - Core NFC framework
- NFCNDEFReaderSession
- NFCTagReaderSession
- NFCNDEFMessage
- NFCNDEFPayload
- NFCNDEFTag
- NFCNDEFReaderSessionDelegate
- NFCTagReaderSessionDelegate
- Building an NFC Tag-Reader App
- Adding Support for Background Tag Reading
- Near Field Communication Tag Reader Session Formats Entitlement
- 扩展模式(ISO 7816命令、多标签扫描、NDEF锁定):
references/nfc-patterns.md - Core NFC框架
- NFCNDEFReaderSession
- NFCTagReaderSession
- NFCNDEFMessage
- NFCNDEFPayload
- NFCNDEFTag
- NFCNDEFReaderSessionDelegate
- NFCTagReaderSessionDelegate
- 构建NFC标签读取应用
- 添加后台标签读取支持
- 近场通信标签读取会话格式权限