NFC | #3 在 iOS App 使用 CoreNFC 實作 NFC 傳輸
前言
工作關係接觸到 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
這裡會發現 NFCTagReaderSession
和 NFCNDEFReaderSession
,兩者都繼承 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
NFCReaderSession
跟 NFCTag
進行連線。成功連線後,檢查 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()
}
NFCNDEFMessage
Apple 用 NFCNDEFMessage
來定義 NDEF Message。NFCNDEFMessage
由一個或數個 NFCNDEFPayload
組成。
NFCNDEFPayload
Apple 用 NFCNDEFPayload
來定義 NDEF Record。NFCNDEFPayload
由 NFCTypeNameFormat
, type
, identifier
, payload
組成。