Wie man NFC für iOS implementiert

9. Oktober 2018

Man hält seine Karte ganz nah an ein Lesegerät und schon ist das Mensa-Essen bezahlt, die Bücher sind ausgeliehen oder die Fahrkarte ist gescannt. Das kennen wir alle. Vergleichsweise neu ist, dass man diese Technologie - Near Field Communication genannt, kurz NFC - auch mit dem iPhone ab Version 7 oder neuer nutzen kann. iOS-Entwickler können diese Funktionalität seit iOS 11 in ihre Apps integrieren. Dafür müssen sie das standardisierte NFC Data Exchange Format (NDEF) einsetzen, auf das Apple seine API für NFC-Tags beschränkt hat und das vom NFC Forum veröffentlicht wurde. Wie man die NFC-Technologie mithilfe von CoreNFC beim Programmieren von Apps nutzt, zeige ich im Folgenden.

Gestaltung der Nutzeroberfläche

Zu Beginn eine gute und eine schlechte Nachricht. Die Schlechte zuerst: Das Lesen von NFC-Tags ist nur mit einem vordefinierten Dialog möglich. Es ist nicht möglich, eine eigene Nutzeroberfläche zur Verfügung zu stellen. Die gute Nachricht: Die Gestaltung des User Interface (UI) ist ziemlich einfach: Das Einzige, was benötigt wird, ist eine kurze Nachricht an den Nutzer, was zu tun ist. Die Sitzung, in der dieser Dialog angezeigt wird, bleibt 60 Sekunden lang aktiv, danach tritt ein Timeout ein und die Sitzung endet automatisch.

Berechtigung

Bevor wir die eigentliche NFC-Funktionalität verwenden können, müssen wir unserer App die Berechtigung dafür erteilen. Dazu wählt man das Projekt im Projektnavigator aus, klickt auf den App-Target und geht zum Capabilities-Tab. Der Screenshot unten zeigt, dass der Schalter neben Near Field Communication Tag Reading auf ON stehen muss. Hierfür unbedingt darauf achten, dass man eine dedizierte App-ID hat! Wildcards funktionieren nicht.

Schalter neben Near Field Communication Tag Reading auf ON

Nachdem wir die Fähigkeit aktiviert haben, erweitern wir unsere info.plist um den Eintrag Privacy - NFC Scan Usage Description. Als Wert muss diesem Eintrag eine detaillierte Erläuterung hinzugefügt werden, die dem Benutzer erklärt, warum wir NFC verwenden.

Los geht's!

Sobald alles wie beschrieben konfiguriert ist, können wir anfangen zu implementieren. Die NFC-Funktionalität ist in der NFCNDEFReaderSession implementiert. Um diese Session nutzen zu können, importieren wir das CoreNFC-Framework, wie am Anfang des folgenden Code-Snippets zu sehen ist.

//
//  ViewController.swift
//  NFC-Sample
//
//  Created by Jens on 08.05.18.
//  Copyright © 2018 Jamit Labs. All rights reserved.
//

import CoreNFC
import UIKit

class ViewController: UITableViewController {
    ...
}

Bevor wir NFCNDEFReaderSession mit der Funktion begin starten können, müssen wir das entsprechende NFCNDEFReaderSessionDelegate implementieren. Letzteres stellt Callback-Funktionen für erkannte Nachrichten und Fehler bereit, wenn etwas schiefgelaufen ist. Die beiden benötigten Funktionen sieht man im folgenden Snippet.

extension ViewController: NFCNDEFReaderSessionDelegate {
    func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        ... 
    }

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

Das wäre erledigt. Jetzt gehen wir weiter zum nächsten Code Snippet und konzentrieren uns auf die Funktion addButtonTriggered: Diese erstellt eine neue NFCNDEFReaderSession mit unserem ViewController als Delegate. Wir verwenden keine Queue, also können wir nil als Parameter angeben und invalidateAfterFirstRead auf true setzen. Dieses Attribut kann nützlich sein, wenn wir mehr als einen Tag scannen wollen, bevor der Benutzer die Sitzung beendet oder das 60-Sekunden-Timeout eintritt. Zudem setzen wir auf die alertMessage-Property, die Nachricht, die dem Benutzer anschließend im Dialogfenster angezeigt werden soll.

//
//  ViewController.swift
//  nfc-test
//
//  Created by Jens on 08.05.18.
//  Copyright © 2018 Jamit Labs. All rights reserved.
//

import CoreNFC
import UIKit

class ViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func addButtonTriggered(_ sender: UIBarButtonItem) {
        let session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
        session.alertMessage = "Hey user hold this iPhone near to a NFC-Tag"
        session.begin()
    }
}

extension ViewController: NFCNDEFReaderSessionDelegate {
    func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        // Handle errors
    }

    func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        guard let message = messages.first, let record = message.records.first else { return }

        // Handle your payload here
        ...
    }
} 

Mit der begonnenen Sitzung können wir unsere ersten NFC-Tags scannen. Mit jedem gescannten Tag wird die Delegate-Funktion readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) aufgerufen. Dadurch erhalten wir ein Array von Messages, wobei jede Message wiederum ein Array von Records enthält (die Payload der Message). Jeder Record kann entweder verschiedene Daten liefern oder aber zusammenhängende Daten enthalten, die als Chunks gespeichert sind. Wie in der ersten Zeile der Delegate-Funktion zu sehen ist, behandeln wir der Einfachheit halber nur den einzelnen Record.

Der Record kann mehrere Arten von Inhalten enthalten, z.B. einfachen Text oder einen vordefinierten URI-Inhalt. Die Property type des Records beschreibt den Payload-Typ in einem einzigen Zeichen: 'T' für Text und 'U' für URI. Die URI-Payload hat ein vordefiniertes Format. Diese Payload muss mit einem einzelnen Byte beginnen, dass das Protokoll der URI bereitstellt. Ein http:// URI beginnt beispielsweise mit dem Bytewert 0x03. Diese Identifikatoren werden vom NFC Forum, wie zuvor beschrieben, veröffentlicht. Eine vollständige Liste aller Werte und weitere Informationen findet man unter Adafruit - Über das NDEF Format.

Wenn ein Fehler auftritt, wird die Delegate-Funktion didInvalidateWithError aufgerufen. Ein typischer Fehler ist zum Beispiel das 60-Sekunden-Timeout. In dem Fall, dass der Benutzer eine Scan-Session abbricht, wird das Delegate mit dem Fehler vom Typ readerSessionInvalidationErrorUserCanceled aufgerufen.

Im folgenden Bild sieht man den Dialog, den der Nutzer sieht, wenn er die Sitzung startet.

NFC-Dialogfenster

Das war schon alles, jetzt kannst du deine NFC-Tags auslesen!

Fazit

Wie wir gesehen haben, ist das Scannen eines NFC-Tags in iOS sehr einfach. Leider gibt es einige Einschränkungen, da nur Tags im NDEF-Format erkannt werden. Das Scannen wird durch ein Session-Design entkoppelt und um ein von Apple bereitgestelltes Dialogfenster vervollständigt. Uns bleibt lediglich die Möglichkeit, eine Nachricht zu definieren, die dem Benutzer angezeigt wird. Selbst das Parsen eines Datensatzes geht durch die Verwendung des standardisierten Formates leicht von der Hand.

In Zukunft wird Apple hoffentlich noch weitere Funktionen, wie das Scannen von RFID-Tags oder das Beschreiben von NFC-Tags anbieten, wie es bei Android schon seit Jahren üblich ist.

Übrigens: Mit den neuen iPhones kann man NFC auch im Hintergrund scannen. Was dazu beim Programmieren zu beachten ist, verraten wir demnächst in einem weiteren Blogbeitrag.