core-nfc

core-nfc

热门

使用 CoreNFC 读取和写入 NFC 标签。适用于扫描 NDEF 标签、读取 ISO7816/ISO15693/FeliCa/MIFARE 标签、写入 NDEF 消息、处理 NFC 会话生命周期、配置 NFC 授权以及在 iOS 应用中实现后台标签读取。

936Star
47Fork
更新于 2026/7/15
SKILL.md
readonly只读
name
core-nfc
description

使用 CoreNFC 读取和写入 NFC 标签。适用于扫描 NDEF 标签、读取 ISO7816/ISO15693/FeliCa/MIFARE 标签、写入 NDEF 消息、处理 NFC 会话生命周期、配置 NFC 授权以及在 iOS 应用中实现后台标签读取。

CoreNFC

使用 CoreNFC 框架在 iPhone 上读取和写入 NFC 标签。涵盖 NDEF 读取器会话、标签读取器会话、NDEF 消息构建、授权和后台标签读取。

目录

设置

项目配置

  1. 在 Xcode 中添加 Near Field Communication Tag Reading 能力
  2. 在 Info.plist 中添加 NFCReaderUsageDescription,并填写面向用户的说明字符串
  3. 添加 com.apple.developer.nfc.readersession.formats 授权,使用当前的 TAG 值;不要添加旧的 NDEF
  4. 对于 ISO 7816 标签,在 Info.plist 的 com.apple.developer.nfc.readersession.iso7816.select-identifiers 中添加支持的应用标识符
  5. 对于 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.readingAvailableNFCTagReaderSession.readingAvailable
  • [ ] 在调用 begin() 前设置了会话委托
  • [ ] 会话无效后将引用置为 nil
  • [ ] didInvalidateWithError 区分了用户取消和实际错误
  • [ ] 在写入操作前查询了 NDEF 状态
  • [ ] 在写入大消息前检查了标签容量
  • [ ] 如果使用 NFCTagReaderSession,在 Info.plist 中列出了 ISO 7816 应用标识符
  • [ ] 当轮询 .iso18092 时,在 Info.plist 中列出了 FeliCa 系统代码
  • [ ] 后台标签读取使用 URI NDEF 记录和通用链接或受支持的系统 URL 方案
  • [ ] 未使用自定义 URL 方案、bundle ID 或任意 NDEF 内容类型进行后台路由
  • [ ] 与支付相关的 AID 未路由到 NFCTagReaderSession
  • [ ] 一次只有一个读取器会话处于活动状态

参考资料