关于 core-nfc
面向 iPhone 应用开发者的 CoreNFC 技能,介绍使用 Core NFC 读取和写入 NFC 标签。内容包括 NDEF 消息、原生标签协议、权限配置及后台标签读取。
主要功能
- 01读取 NDEF 标签内容
- 02读写 ISO 7816、ISO 15693、FeliCa 和 MIFARE 标签
- 03构造和解析 NDEF 消息
- 04配置 NFC 权限与会话
- 05使用 URI 标签进行后台读取
适用场景
- 01读取标签中的网址或文本
- 02向可读写标签写入 NDEF 消息
- 03实现特定协议标签交互
- 04通过通用链接从标签打开应用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill core-nfc安装后在 Claude Code 中输入/core-nfc确认已加载。
附带文件evals/evals.jsonreferences/nfc-patterns.md
详细介绍
CoreNFC
使用 CoreNFC 框架在 iPhone 上读取和写入 NFC 标签。涵盖 NDEF 读取器会话、标签读取器会话、NDEF 消息构建、授权配置和后台标签读取。
内容
设置
项目配置
- 在 Xcode 中添加 近场通信标签读取功能
- 在 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 界面或会话前,务必检查读取器会话是否可用。使用你将要创建的具体读取器会话类型。
import CoreNFC
guard NFCNDEFReaderSession.readingAvailable else {
// Device does not support NFC or feature is restricted
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 = "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
}
}
使用标签连接进行读取
对于读写操作,请使用标签检测代理方法连接到单个标签:
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()
}
}
}
}
标签读取器会话
需要直接访问标签的原生协议(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: "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 负载类型
创建常见负载
// 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)!
)
解析负载内容
加载解析 NDEF 负载内容,查看完整的类型名称格式分支和多记录处理方式。
后台标签读取
在 iPhone XS 及更新机型上,iOS 无需打开你的应用即可在后台读取 NFC 标签。NDEF 消息必须包含 URI 记录(typeNameFormat == .nfcWellKnown,类型为 U)。如果有多条 URI 记录,系统会使用第一条。
要进行应用专属路由,请在标签中写入通用链接,并为该域配置关联域名功能。后台标签读取还支持特定的系统 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 的旧示例。
不要:忽略会话失效错误
会话可能因多种原因失效。区分用户取消和实际错误,可以避免触发不必要的错误提醒。
// 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
}
不要:持有已失效会话的强引用
会话一旦失效,就无法重启。将引用设为 nil,并为下一次扫描创建新会话。
// 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()
}
审查清单
- 已在 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 方案、捆绑 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 系统代码