core-nfc

core-nfc

熱門

使用 CoreNFC 讀寫 NFC 標籤。適用於掃描 NDEF 標籤、讀取 ISO7816/ISO15693/FeliCa/MIFARE 標籤、寫入 NDEF 訊息、處理 NFC 會話生命週期、設定 NFC 授權,或在 iOS App 中實作背景標籤讀取。

936星標
47分支
更新於 2026/7/15
SKILL.md
唯讀
名稱
core-nfc
描述

使用 CoreNFC 讀寫 NFC 標籤。適用於掃描 NDEF 標籤、讀取 ISO7816/ISO15693/FeliCa/MIFARE 標籤、寫入 NDEF 訊息、處理 NFC 會話生命週期、設定 NFC 授權,或在 iOS App 中實作背景標籤讀取。

CoreNFC

在 iPhone 上使用 CoreNFC 框架讀寫 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 標籤,在 Info.plist 的 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)!
)

解析承載內容

請載入 Parsing NDEF Payload Content 以取得完整的類型名稱格式切換及多記錄處理。

背景標籤讀取

在 iPhone XS 及後續機型上,iOS 可以在背景讀取 NFC 標籤,無需開啟你的 App。NDEF 訊息必須包含一個 URI 記錄(typeNameFormat == .nfcWellKnown,類型 U)。如果有多個 URI 記錄,系統會使用第一個。

若要進行 App 特定的路由,請將通用連結寫入標籤,並為該網域設定 Associated Domains 功能。背景標籤讀取也支援特定的系統 URL 方案,例如網頁、電子郵件、簡訊、電話、FaceTime、地圖和 HomeKit 設定。它不支援自訂 URL 方案,且系統不會根據 bundle ID 或任意 NDEF 內容類型進行路由。

當使用者點擊相容的標籤時,iOS 會顯示一個通知,開啟你的 App。透過 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 硬體。請使用目前的 TAG 值作為 Core NFC 讀取器會話;不要複製加入 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
  • [ ] 一次只有一個讀取器會話處於活動狀態

參考資料