mcpField
C
core-nfc

core-nfc

github.com/dpearson2699開発1.2k3.6k 回インストール13 週間前に更新
リポジトリ

core-nfc について

Core NFCを使ったNFCタグの読み書きを扱う、iPhoneアプリ開発者向けスキルです。NDEFメッセージ、タグ固有のプロトコル、機能設定、バックグラウンド読み取りを解説します。

主な機能

  1. 01NDEFタグの内容を読み取る
  2. 02ISO 7816、ISO 15693、FeliCa、MIFAREタグの読み書き
  3. 03NDEFメッセージの作成と解析
  4. 04NFCアクセスとセッションの設定
  5. 05URIタグによるバックグラウンド読み取り

活用シーン

  1. 01タグに記録されたURLやテキストの読み取り
  2. 02書き込み可能なタグへのNDEFメッセージ書き込み
  3. 03特定プロトコルを使ったタグ操作
  4. 04ユニバーサルリンクによるタグからのアプリ起動

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill core-nfc

インストール後、Claude Code で/core-nfcを入力して読み込まれたか確認します。

同梱ファイルevals/evals.jsonreferences/nfc-patterns.md

詳細

CoreNFC

CoreNFC フレームワークを使用して、iPhone で NFC タグを読み書きします。NDEF リーダーセッション、タグリーダーセッション、NDEF メッセージの構築、エンタイトルメント、バックグラウンドでのタグ読み取りについて説明します。

目次

セットアップ

プロジェクトの設定
  1. Xcode で Near Field Communication Tag Reading ケイパビリティを追加します
  2. Info.plist に NFCReaderUsageDescription を追加し、ユーザー向けの理由を記述します
  3. 現行の TAG 値を指定した com.apple.developer.nfc.readersession.formats エンタイトルメントを追加します。レガシーな NDEF は追加しないでください
  4. ISO 7816 タグの場合、Info.plist の com.apple.developer.nfc.readersession.iso7816.select-identifiers に対応するアプリケーション識別子を追加します
  5. FeliCa タグの場合、com.apple.developer.nfc.readersession.felica.systemcodes に対応するシステムコードを追加します。ワイルドカードのシステムコードは使用しないでください
デバイス要件

NFC の読み取りには iPhone 7 以降が必要です。NFC の UI やセッションを作成する前に、リーダーセッションが利用可能か必ず確認してください。これから作成する具体的なリーダーセッションの種類を使用してください。

import CoreNFC

guard NFCNDEFReaderSession.readingAvailable else {
    // Device does not support NFC or feature is restricted
    showUnsupportedMessage()
    return
}
主な型
型役割
NFCNDEFReaderSessionNDEF 形式のタグをスキャンします
NFCTagReaderSessionISO7816、ISO15693、FeliCa、MIFARE タグをスキャンします
NFCNDEFMessageNDEF ペイロードレコードのコレクションです
NFCNDEFPayloadNDEF メッセージ内の単一レコードです
NFCNDEFTagNDEF 対応タグとやり取りするためのプロトコルです

NDEF リーダーセッション

タグから NDEF 形式のデータを読み取るには、NFCNDEFReaderSession を使用します。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 = "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
    }
}
タグ接続による読み取り

読み書き操作では、タグ検出デリゲートメソッドを使用して、個々のタグに接続します。

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()
            }
        }
    }
}

タグリーダーセッション

ネイティブのタグプロトコル(ISO 7816、ISO 15693、FeliCa、MIFARE)に直接アクセスする必要がある場合は、NFCTagReaderSession を使用します。

ポーリングオプションタグ
.iso14443ISO 7816 互換タグおよび MIFARE
.iso15693ISO 15693
.iso18092FeliCa

決済関連の AID には、このセッションを使用しないでください。プロトコル固有の接続、APDU、コマンド、レスポンスの処理については、nfc-patterns.md を読み込んでください。

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: "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 ペイロードの種類

一般的なペイロードの作成
// 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)!
)
ペイロードの内容の解析

完全な type-name-format の switch 文と複数レコードの処理については、NDEF ペイロードの内容の解析 を読み込んでください。

バックグラウンドでのタグ読み取り

iPhone XS 以降では、アプリを開かなくても iOS がバックグラウンドで NFC タグを読み取れます。NDEF メッセージには URI レコード(typeNameFormat == .nfcWellKnown、type U)が含まれている必要があります。URI レコードが複数ある場合、システムは最初のレコードを使用します。

アプリ固有のルーティングには、タグにユニバーサルリンクを書き込み、そのドメインに対して Associated Domains ケイパビリティを設定します。バックグラウンドでのタグ読み取りは、Web、メール、SMS、電話、FaceTime、Maps、HomeKit の設定など、特定のシステム URL スキームにも対応しています。カスタム URL スキームには対応しておらず、システムはバンドル ID や任意の NDEF コンテンツタイプに基づいてルーティングしません。

ユーザーが対応するタグにタップすると、iOS はアプリを開く通知を表示します。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 ハードウェアにアクセスできません。Core NFC のリーダーセッションには現行の TAG 値を使用してください。NDEF を追加する古い例をそのままコピーしないでください

しないこと:セッション無効化エラーを無視する

セッションはさまざまな理由で無効になります。ユーザーによるキャンセルと実際のエラーを区別することで、誤ったエラー通知を防げます。

// 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
}
しないこと:無効になったセッションへの強い参照を保持する

セッションは無効になると、再開できません。参照を nil にし、次回のスキャン時に新しいセッションを作成してください。

// 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()
}

レビューチェックリスト

  • Signing & Capabilities で NFC 機能を追加している
  • NFCReaderUsageDescription を Info.plist に設定している
  • com.apple.developer.nfc.readersession.formats エンタイトルメントで、旧式の NDEF ではなく TAG を使用している
  • セッションを作成する前に NFCNDEFReaderSession.readingAvailable または NFCTagReaderSession.readingAvailable を確認している
  • begin() の呼び出し前にセッションのデリゲートを設定している
  • 無効化後にセッションへの参照を nil にしている
  • didInvalidateWithError でユーザーによるキャンセルと実際のエラーを区別している
  • 書き込み操作の前に NDEF ステータスを問い合わせている
  • 大きなメッセージを書き込む前にタグの容量を確認している
  • NFCTagReaderSession を使用する場合、ISO 7816 アプリケーション識別子を Info.plist に記載している
  • .iso18092 のポーリング時に FeliCa システムコードを Info.plist に記載している
  • バックグラウンドでのタグ読み取りに URI NDEF レコードとユニバーサルリンク、またはサポートされているシステム URL スキームを使用している
  • バックグラウンドでのルーティングにカスタム URL スキーム、バンドル ID、任意の NDEF コンテンツタイプを使用していない
  • 決済関連の AID を NFCTagReaderSession 以外にルーティングしている
  • 一度にアクティブにするリーダーセッションを 1 つだけにしている

参考資料

コメント