core-nfc by dpearson2699/swift-ios-skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill core-nfc使用 CoreNFC 框架在 iPhone 上读取和写入 NFC 标签。涵盖 NDEF 读取器会话、标签读取器会话、NDEF 消息构造、权限和后台标签读取。目标为 Swift 6.2 / iOS 26+。
NFCReaderUsageDescription 并提供一个面向用户的原因字符串com.apple.developer.nfc.readersession.formats 权限,并设置你的应用要读取的标签类型(例如 NDEF、TAG)com.apple.developer.nfc.readersession.iso7816.select-identifiers 中广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
NFC 读取需要 iPhone 7 或更高版本。在呈现 NFC 用户界面之前,务必检查读取器会话的可用性。
import CoreNFC
guard NFCNDEFReaderSession.readingAvailable else {
// 设备不支持 NFC 或功能受限
showUnsupportedMessage()
return
}
| 类型 | 作用 |
|---|---|
NFCNDEFReaderSession | 扫描 NDEF 格式的标签 |
NFCTagReaderSession | 扫描 ISO7816、ISO15693、FeliCa、MIFARE 标签 |
NFCNDEFMessage | NDEF 负载记录的集合 |
NFCNDEFPayload | NDEF 消息中的单个记录 |
NFCNDEFTag | 用于与支持 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。
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
}
}
将 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()
}
}
}
}
// 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)!
)
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
}
}
在 iPhone XS 及更高版本上,iOS 可以在后台读取 NFC 标签而无需打开你的应用。要启用此功能:
当用户点击兼容的标签时,iOS 会显示一个通知来打开你的应用。通过 NSUserActivity 处理标签数据:
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)
}
}
}
没有 com.apple.developer.nfc.readersession.formats 权限,会话创建会在运行时崩溃。
// 错误 -- 未添加权限,会崩溃
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)
// 正确 -- 先在 Signing & Capabilities 中添加权限
// 然后相同的代码即可工作:
let session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: true
)
在不支持的设备(iPad、iPod touch 或 iPhone 6s 及更早版本)上尝试创建 NFC 会话会导致崩溃。
// 错误
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()
}
会话可能因多种原因而失效。区分用户取消和真实错误可以防止虚假的错误警报。
// 错误 -- 当用户取消时也显示错误
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()
}
向只读标签写入会静默失败或产生令人困惑的错误。
// 错误 -- 写入前未检查状态
tag.writeNDEF(message) { error in
// 在只读标签上可能会失败
}
// 正确 -- 先检查状态
tag.queryNDEFStatus { status, capacity, error in
guard status == .readWrite else {
session.invalidate(errorMessage: "标签是只读的。")
return
}
tag.writeNDEF(message) { error in
// 处理结果
}
}
NFCReaderUsageDescriptioncom.apple.developer.nfc.readersession.formats 权限并设置了正确的标签类型NFCNDEFReaderSession.readingAvailablebegin() 之前设置了会话委托didInvalidateWithError 区分了用户取消和实际错误NFCTagReaderSession,已在 Info.plist 中列出 ISO 7816 应用程序标识符references/nfc-patterns.md每周安装量
330
代码仓库
GitHub 星标数
269
首次出现
2026年3月8日
安全审计
安装于
codex327
opencode324
github-copilot324
amp324
cline324
kimi-cli324
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+.
NFCReaderUsageDescription to Info.plist with a user-facing reason stringcom.apple.developer.nfc.readersession.formats entitlement with the tag types your app reads (e.g., NDEF, TAG)com.apple.developer.nfc.readersession.iso7816.select-identifiers in Info.plistNFC reading requires iPhone 7 or later. Always check for reader session availability before presenting NFC UI.
import CoreNFC
guard NFCNDEFReaderSession.readingAvailable else {
// Device does not support NFC or feature is restricted
showUnsupportedMessage()
return
}
| Type | Role |
|---|---|
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 |
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.
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
}
}
For read-write operations, use the tag-detection delegate method to connect to individual tags:
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()
}
}
}
}
Use NFCTagReaderSession when you need direct access to the native tag protocol (ISO 7816, ISO 15693, FeliCa, or MIFARE).
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
}
}
Write NDEF data to a connected tag. Always check readWrite status first.
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()
}
}
}
}
// 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)!
)
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
}
}
On iPhone XS and later, iOS can read NFC tags in the background without opening your app. To opt in:
When a user taps a compatible tag, iOS displays a notification that opens your app. Handle the tag data via NSUserActivity:
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)
}
}
}
Without the com.apple.developer.nfc.readersession.formats entitlement, session creation crashes at runtime.
// 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
)
Attempting to create an NFC session on an unsupported device (iPad, iPod touch, or iPhone 6s and earlier) crashes.
// 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()
}
The session invalidates for multiple reasons. Distinguishing user cancellation from real errors prevents false error alerts.
// 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
}
Once a session is invalidated, it cannot be restarted. Nil out your reference and create a new session for the next scan.
// 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()
}
Writing to a read-only tag silently fails or produces confusing errors.
// 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
}
}
NFCReaderUsageDescription set in Info.plistcom.apple.developer.nfc.readersession.formats entitlement configured with correct tag typesNFCNDEFReaderSession.readingAvailable checked before creating sessionsbegin()didInvalidateWithError distinguishes user cancellation from actual errorsNFCTagReaderSessionreferences/nfc-patterns.mdWeekly Installs
330
Repository
GitHub Stars
269
First Seen
Mar 8, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
codex327
opencode324
github-copilot324
amp324
cline324
kimi-cli324