使用 CoreNFC 读取和写入 NFC 标签。适用于扫描 NDEF 标签、读取 ISO7816/ISO15693/FeliCa/MIFARE 标签、写入 NDEF 消息、处理 NFC 会话生命周期、配置 NFC 授权以及在 iOS 应用中实现后台标签读取。
CoreNFC
使用 CoreNFC 框架在 iPhone 上读取和写入 NFC 标签。涵盖 NDEF 读取器会话、标签读取器会话、NDEF 消息构建、授权和后台标签读取。
目录
设置
项目配置
- 在 Xcode 中添加 Near Field Communication Tag Reading 能力
- 在 Info.plist 中添加
NFCReaderUsageDescription,并填写面向用户的说明字符串 - 添加
com.apple.developer.nfc.readersession.formats授权,使用当前的TAG值;不要添加旧的NDEF - 对于 ISO 7816 标签,在 Info.plist 的
com.apple.developer.nfc.readersession.iso7816.select-identifiers中添加支持的应用标识符 - 对于 FeliCa 标签,在
com.apple.developer.nfc.readersession.felica.systemcodes中添加支持的系统代码;不要使用通配符系统代码
设备要求
NFC 读取需要 iPhone 7 或更高版本。在创建 NFC UI 或会话之前,务必检查读取器会话的可用性。使用即将创建的具体读取器会话类型。
import CoreNFC
guard NFCNDEFReaderSession.readingAvailable else {
// 设备不支持 NFC 或功能受限
showUnsupportedMessage()
return
}
关键类型
| 类型 | 作用 |
|---|---|
NFCNDEFReaderSession |
扫描 NDEF 格式的标签 |
NFCTagReaderSession |
扫描 ISO7816、ISO15693、FeliCa、MIFARE 标签 |
NFCNDEFMessage |
NDEF 负载记录的集合 |
NFCNDEFPayload |
NDEF 消息中的单个记录 |
NFCNDEFTag |
与支持 NDEF 的标签交互的协议 |
NDEF 读取器会话
使用 NFCNDEFReaderSession 从标签读取 NDEF 格式的数据。这是读取标准标签内容(如 URL、文本和 MIME 数据)的最简单途径。
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
}
}
通过标签连接读取
对于读写操作,使用标签检测委托方法连接到单个标签:
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()
}
}
}
}
标签读取器会话
当需要直接访问原生标签协议(ISO 7816、ISO 15693、FeliCa 或 MIFARE)时,使用 NFCTagReaderSession。
| 轮询选项 | 标签 |
|---|---|
.iso14443 |
ISO 7816 兼容和 MIFARE |
.iso15693 |
ISO 15693 |
.iso18092 |
FeliCa |
不要将此会话用于与支付相关的 AID。加载 nfc-patterns.md 以获取协议特定的连接、APDU、命令和响应处理。
写入 NDEF 消息
将 NDEF 数据写入连接的标签。务必先检查 readWrite 状态。
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 负载类型
创建常见负载
// 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)!
)
解析负载内容
加载 解析 NDEF 负载内容 以获取完整的类型名称格式切换和多记录处理。
后台标签读取
在 iPhone XS 及更高版本上,iOS 可以在后台读取 NFC 标签,无需打开您的应用。NDEF 消息必须包含一个 URI 记录(typeNameFormat == .nfcWellKnown,类型 U)。如果有多个 URI 记录,系统使用第一个。
对于应用特定的路由,将通用链接写入标签,并为该域配置 Associated Domains 能力。后台标签读取还支持特定的系统 URL 方案,如网页、电子邮件、短信、电话、FaceTime、地图和 HomeKit 设置。它不支持自定义 URL 方案,并且系统不会按 bundle ID 或任意 NDEF 内容类型进行路由。
当用户点击兼容的标签时,iOS 会显示一个通知,打开您的应用。通过 NSUserActivity 处理标签数据:
func 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)
}
}
常见错误
不要:使用过时或缺失的 NFC 授权
如果没有 com.apple.developer.nfc.readersession.formats 授权,读取器会话无法访问 NFC 硬件。对于 Core NFC 读取器会话,使用当前的 TAG 值;不要复制添加 NDEF 的旧示例。
不要:忽略会话无效错误
会话因多种原因无效。区分用户取消和实际错误可以防止错误的错误提示。
// 错误——用户取消时显示错误
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
}
不要:持有对过期会话的强引用
一旦会话无效,就无法重新启动。将引用置为 nil,并为下一次扫描创建新会话。
// 错误——重用无效的会话
func scanAgain() {
session?.begin() // 无效,会话已失效
}
// 正确——创建新会话
func scanAgain() {
session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: false
)
session?.begin()
}
审查清单
- [ ] 在 Signing & Capabilities 中添加了 NFC 能力
- [ ] 在 Info.plist 中设置了
NFCReaderUsageDescription - [ ]
com.apple.developer.nfc.readersession.formats授权使用TAG,而不是旧的NDEF - [ ] 在创建会话前检查了
NFCNDEFReaderSession.readingAvailable或NFCTagReaderSession.readingAvailable - [ ] 在调用
begin()前设置了会话委托 - [ ] 会话无效后将引用置为 nil
- [ ]
didInvalidateWithError区分了用户取消和实际错误 - [ ] 在写入操作前查询了 NDEF 状态
- [ ] 在写入大消息前检查了标签容量
- [ ] 如果使用
NFCTagReaderSession,在 Info.plist 中列出了 ISO 7816 应用标识符 - [ ] 当轮询
.iso18092时,在 Info.plist 中列出了 FeliCa 系统代码 - [ ] 后台标签读取使用 URI NDEF 记录和通用链接或受支持的系统 URL 方案
- [ ] 未使用自定义 URL 方案、bundle ID 或任意 NDEF 内容类型进行后台路由
- [ ] 与支付相关的 AID 未路由到
NFCTagReaderSession - [ ] 一次只有一个读取器会话处于活动状态
参考资料
- 扩展模式(ISO 7816 命令、多标签扫描、NDEF 锁定):references/nfc-patterns.md
- Core NFC 框架
- NFCNDEFReaderSession
- NFCTagReaderSession
- NFCNDEFMessage
- NFCNDEFPayload
- NFCNDEFTag
- NFCNDEFReaderSessionDelegate
- NFCTagReaderSessionDelegate
- 构建 NFC 标签读取器应用
- 添加后台标签读取支持
- 近场通信标签读取器会话格式授权
- NFC 标签读取器会话的 ISO7816 应用标识符
- NFC 标签读取器会话的 ISO18092 系统代码






