mcpField
C
core-nfc

core-nfc

github.com/dpearson2699开发1.2k3.6k 次安装13 周前更新
代码仓库

关于 core-nfc

面向 iPhone 应用开发者的 CoreNFC 技能,介绍使用 Core NFC 读取和写入 NFC 标签。内容包括 NDEF 消息、原生标签协议、权限配置及后台标签读取。

主要功能

  1. 01读取 NDEF 标签内容
  2. 02读写 ISO 7816、ISO 15693、FeliCa 和 MIFARE 标签
  3. 03构造和解析 NDEF 消息
  4. 04配置 NFC 权限与会话
  5. 05使用 URI 标签进行后台读取

适用场景

  1. 01读取标签中的网址或文本
  2. 02向可读写标签写入 NDEF 消息
  3. 03实现特定协议标签交互
  4. 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 消息构建、授权配置和后台标签读取。

内容

设置

项目配置
  1. 在 Xcode 中添加 近场通信标签读取功能
  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 界面或会话前,务必检查读取器会话是否可用。使用你将要创建的具体读取器会话类型。

import CoreNFC

guard NFCNDEFReaderSession.readingAvailable else {
    // Device does not support NFC or feature is restricted
    showUnsupportedMessage()
    return
}
关键类型
类型作用
NFCNDEFReaderSession扫描 NDEF 格式的标签
NFCTagReaderSession扫描 ISO7816、ISO15693、FeliCa、MIFARE 标签
NFCNDEFMessageNDEF 负载记录集合
NFCNDEFPayloadNDEF 消息中的单条记录
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
.iso15693ISO 15693
.iso18092FeliCa

请勿将此会话用于支付相关的 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 之外
  • 同一时间仅有一个读取器会话处于活动状态

参考资料

评论

支持 Markdown