core-nfc

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CoreNFC

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

项目配置

  1. Add the Near Field Communication Tag Reading capability in Xcode
  2. Add
    NFCReaderUsageDescription
    to Info.plist with a user-facing reason string
  3. Add the
    com.apple.developer.nfc.readersession.formats
    entitlement with the tag types your app reads (e.g.,
    NDEF
    ,
    TAG
    )
  4. For ISO 7816 tags, add supported application identifiers to
    com.apple.developer.nfc.readersession.iso7816.select-identifiers
    in Info.plist
  1. 在Xcode中添加Near Field Communication Tag Reading能力
  2. 在Info.plist中添加
    NFCReaderUsageDescription
    字段,并填写面向用户的权限说明字符串
  3. 添加
    com.apple.developer.nfc.readersession.formats
    权限,并配置应用支持的标签类型(例如:
    NDEF
    ,
    TAG
  4. 若使用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

核心类型

TypeRole
NFCNDEFReaderSession
Scans for NDEF-formatted tags
NFCTagReaderSession
Scans for ISO7816, ISO15693, FeliCa, MIFARE tags
NFCNDEFMessage
Collection of NDEF payload records
NFCNDEFPayload
Single record within an NDEF message
NFCNDEFTag
Protocol for interacting with an NDEF-capable tag
类型作用
NFCNDEFReaderSession
扫描NDEF格式的标签
NFCTagReaderSession
扫描ISO7816、ISO15693、FeliCa、MIFARE标签
NFCNDEFMessage
NDEF载荷记录的集合
NFCNDEFPayload
NDEF消息中的单个记录
NFCNDEFTag
与支持NDEF的标签交互的协议

NDEF Reader Session

NDEF读取会话

Use
NFCNDEFReaderSession
to read NDEF-formatted data from tags. This is the simplest path for reading standard tag content like URLs, text, and MIME data.
swift
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
    }
}
使用
NFCNDEFReaderSession
读取标签中的NDEF格式数据。这是读取URL、文本、MIME数据等标准标签内容的最简方式。
swift
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
NFCTagReaderSession
when you need direct access to the native tag protocol (ISO 7816, ISO 15693, FeliCa, or MIFARE).
swift
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)时,使用
NFCTagReaderSession
swift
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
readWrite
status first.
swift
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数据。务必先检查标签的
readWrite
状态。
swift
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:
  1. Add associated domains or universal links that match the URL on your tags
  2. Register your app for the tag's NDEF content type
  3. 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
NSUserActivity
:
swift
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标签,无需打开应用。如需启用该功能:
  1. 添加与标签上URL匹配的关联域名或通用链接
  2. 为标签的NDEF内容类型注册应用
  3. 在标签的NDEF记录中包含应用的Bundle ID
当用户点击兼容的标签时,iOS会显示通知并打开应用。通过
NSUserActivity
处理标签数据:
swift
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
com.apple.developer.nfc.readersession.formats
entitlement, session creation crashes at runtime.
swift
// 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.formats
权限,会话创建会在运行时崩溃。
swift
// 错误示例 -- 未添加权限,会崩溃
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
  • NFCReaderUsageDescription
    set in Info.plist
  • com.apple.developer.nfc.readersession.formats
    entitlement configured with correct tag types
  • NFCNDEFReaderSession.readingAvailable
    checked before creating sessions
  • Session delegate set before calling
    begin()
  • Session reference set to nil after invalidation
  • didInvalidateWithError
    distinguishes user cancellation from actual errors
  • 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状态
  • 写入大消息前检查了标签容量
  • 若使用
    NFCTagReaderSession
    ,在Info.plist中列出了ISO 7816应用标识符
  • 如需后台标签读取,已配置关联域名
  • 同一时间仅存在一个活跃的读取会话

References

参考资料