NFC | #3 在 iOS App 使用 CoreNFC 實作 NFC 傳輸

黃暉德 Wade Huang
17 min readJan 24, 2024

--

前言

工作關係接觸到 NFC 技術,延續上一篇繼續分享 NFC。

目錄

  • What is CoreNFC
  • New a project
    Configure the App to Detect NFC Tags
    – Add an App capacity
    – Add an NFC entitlement
    – Add an NFCReaderUsageDescription
  • Get started
    Import CoreNFC
    Add NFCReaderSession
    ReadingAvailable
    Conform protocols
    Session connect
  • NFCNDEFTag
  • NFCNDEFMessage
  • NFCNDEFPayload

What is CoreNFC?

Apple 在 iOS 13 提供 CoreNFC framework 來實作 NFC 應用,它可以讀取 NFC tag type 1 到 5 ,也支援讀取特定通訊協定的 NFC tag ,例如:ISO 7816, ISO 15693, FeliCa™, and MIFARE® tags。

還有一個重點:

CoreNFC 目前並沒有支援付費相關功能的 NFC 應用。

New a project

建立一個新的專案。

Configure the App to Detect NFC Tags

Add an App capacity

首先要先進入 Target > Signing & Capabilities ,點選 + Capability 新增 NFC 功能。

新增成功後,下方會出現 Near Field Communication Tag Reading 的 capability。

Add an NFC entitlement

專案的目錄也會同時新增 Near Field Communication Tag Reader Session Formats Entitlement。點進去看的話,預設應該只有 TAG ,可以手動新增 NDEF,讓 App 可以掃描對應的標籤格式。

Add an NFCReaderUsageDescription

在 Info.plist 新增 NFCReaderUsageDescription key,value 則設定描述為什麼 App 需要使用 NFC 功能的原因,若沒設定 app 會閃退。

啟動 NFC capability 的相關設定大致上完成了,接下來就開始著手在寫程式的部分。

Get Started

Import CoreNFC

匯入 CoreNFC framework。

import CoreNFC

Add NFCReaderSession

這裡會發現 NFCTagReaderSessionNFCNDEFReaderSession,兩者都繼承 NFCReaderSession ,都可以實作讀寫。

NFCTagReaderSession

NFCTagReaderSession 支援 iOS 13 或以上的版本,通用於讀寫不同類型的 NFC tag,其中包含 NDEF, ISO14443, ISO 15693, feliCa, MiFare..等。

var tagReaderSession: NFCTagReaderSession?

tagReaderSession = NFCTagReaderSession(
pollingOption: [.iso14443, .iso15693],
delegate: self,
queue: nil)

pollingOptions 是一個陣列,可以告訴 SDK 要掃描哪些標籤類型。
iso14443 掃描 ISO 7816-compatible 和 MIFARE 標籤。
iso15693 掃描 ISO 15693 標籤。
iso18092 掃描 FeliCa 標籤。
pace 掃描需要認證的標籤。

NFCNDEFReaderSession

NFCNDEFReaderSession 支援 iOS 13 或以上的版本,專門處理 NDEF 的 NFC tag。

var ndefReaderSession: NFCNDEFReaderSession?

ndefReaderSession = NFCNDEFReaderSession(
delegate: self,
queue: nil,
invalidateAfterFirstRead: false)

ReadingAvailable

在掃描之前可以檢查你的 iPhone 有沒有支援 NFC scanning。

guard NFCNDEFReaderSession.readingAvailable else {
let alertController = UIAlertController(
title: "Scanning Not Supported",
message: "This device doesn't support tag scanning.",
preferredStyle: .alert
)
let okAlertAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(okAlertAction)
self.present(alertController, animated: true, completion: nil)
return
}

Conform protocols

依據使用的 ReaderSession 去遵從對應的 protocol。

NFCTagReaderSessionDelegate

實作來自 NFCTagReaderSession 的 callback。

class NFCReaderViewController: UIViewController, NFCTagReaderSessionDelegate {

// MARK: - NFCTagReaderSessionDelegate

func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
}

func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
}

func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
}
}

NFCNDEFReaderSessionDelegate

實作來自 NFCNDEFReaderSession 的 callback。

class NFCReaderViewController: UIViewController, NFCNDEFReaderSessionDelegate {

// MARK: - NFCNDEFReaderSessionDelegate

func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
}

func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
}

func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
}

func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
}
}

Session connect

NFCReaderSessionNFCTag 進行連線。成功連線後,檢查 tag 狀態確保能執行讀取資料或寫入資料。

func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
if tags.count > 1 {
session.invalidate(errorMessage: "More than 1 tags was found. Please present only 1 tag.")
session.restartPolling()
return
}

// Start to connect to tag
let tag = tags.first!
session.connect(to: tag) { error in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}

// Connect succeeded, then convert to NFCNDEFTag
// ...
}
}

NFCNDEFTag

讀取或寫入資料前,若型別為 NFCTag ,需要先轉型成 NFCNDEFTag

// Connect succeeded, then convert to NFCNDEFTag
var ndefTag: NFCNDEFTag
var identifier: Data
switch tag {
case let .miFare(nfcMiFareTag):
ndefTag = nfcMiFareTag
identifier = nfcMiFareTag.identifier
case let .iso15693(nfcISO15693Tag):
ndefTag = nfcISO15693Tag
identifier = nfcISO15693Tag.identifier
case let .iso7816(nfcISO7816Tag):
ndefTag = nfcISO7816Tag
identifier = nfcISO7816Tag.identifier
case let .feliCa(nfcFeliCaTag):
ndefTag = nfcFeliCaTag
identifier = nfcFeliCaTag.currentIDm
@unknown default:
session.invalidate(errorMessage: "Tag is not valid")
return
}
// Query status
ndefTag.queryNDEFStatus { status, capacity, error in
if let error = error {
self.session?.invalidate(errorMessage: error.localizedDescription)
return
}

if status == .notSupported {
self.session?.invalidate(errorMessage: "Tag is not supported.")
return
} else if status == .readOnly {
self.session?.invalidate(errorMessage: "Tag is read-only.")
return
} else {
// Read NDEF and write NDEF here
// ...
}
}
// Read NDEF
ndefTag.readNDEF { message, error in
if let error = error, message == nil {
self.session?.invalidate(errorMessage: error.localizedDescription)
return
}

session.alertMessage = "Read succeeded."
session.invalidate()

// Read succeeded, then update UI with NFCNDEFMessage
// ...
}
// Create a NFCNDEFMessage
let message = NFCNDEFMessage(records: self.records)

if message.length > capacity {
session.invalidate(errorMessage: "Tag capacity is too small. Minimum size requirement is \(message.length) bytes.")
return
}

// Write NDEF
ndefTag.writeNDEF(message) { error in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}

// Write succeeded
session.alertMessage = "Write NDEFMessage succeeded"
session.invalidate()
}
https://www.oreilly.com/api/v2/epubs/9781449324094/files/images/bnfc_0302.png

NFCNDEFMessage

Apple 用 NFCNDEFMessage 來定義 NDEF Message。NFCNDEFMessage 由一個或數個 NFCNDEFPayload 組成。

NFCNDEFPayload

Apple 用 NFCNDEFPayload 來定義 NDEF Record。NFCNDEFPayloadNFCTypeNameFormat, type, identifier, payload 組成。

--

--