Near Field Communication (NFC) allows devices to communicate wirelessly over short distances, making it a useful technology for tasks such as payments, access control, and information sharing. In this guide, we’ll walk through the process of integrating Core NFC into an iOS application using Swift. We’ll cover the basics of setting up the project, reading NFC tags, and handling the data.

Setting Up the Project

  1. Open Xcode and Create a New Project:
    • Select the “App” template.
    • Name your project (e.g., “NFCReader”).
    • Choose Swift as the language and SwiftUI as the user interface.
  2. Configure Privacy Settings:
    • Open Info.plist.
    • Add the key NFCReaderUsageDescription with a description like “We need access to NFC to scan tags”.

Understanding Core NFC

Core NFC provides the capability to read Near Field Communication (NFC) tags of types 1 through 5 that contain data in NDEF (NFC Data Exchange Format) format. iOS apps using Core NFC can read NFC tags, but they cannot write to NFC tags or communicate with devices over NFC.

Designing the User Interface

  1. Open ContentView.swift and set up a basic UI with a button to start scanning for NFC tags and a text field to display the scanned data.

import SwiftUI

struct ContentView: View {
    @State private var isShowingScanner = false
    @State private var scannedData = ""

    var body: some View {
        VStack {
            Text("Scanned NFC Data:")
                .font(.headline)
            Text(scannedData)
                .font(.largeTitle)
                .padding()

            Button(action: {
                isShowingScanner = true
            }) {
                Text("Start Scanning")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
        .sheet(isPresented: $isShowingScanner) {
            NFCScannerView(scannedData: $scannedData)
        }
    }
}

Creating the NFC Scanner View

  1. Create a new Swift file named NFCScannerView.swift.
  2. Import the necessary frameworks and set up the UIViewControllerRepresentable to bridge UIKit with SwiftUI.

import SwiftUI
import CoreNFC

struct NFCScannerView: UIViewControllerRepresentable {
    @Binding var scannedData: String

    func makeCoordinator() -> Coordinator {
        Coordinator(scannedData: $scannedData)
    }

    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = NFCScannerViewController()
        viewController.delegate = context.coordinator
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }

    class Coordinator: NSObject, NFCNDEFReaderSessionDelegate {
        @Binding var scannedData: String

        init(scannedData: Binding<String>) {
            _scannedData = scannedData
        }

        func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
            // Handle errors
            print("NFC Session invalidated: \(error.localizedDescription)")
        }

        func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
            for message in messages {
                for record in message.records {
                    if let string = String(data: record.payload, encoding: .utf8) {
                        DispatchQueue.main.async {
                            self.scannedData = string
                        }
                    }
                }
            }
        }
    }
}

Implementing the NFC Scanner View Controller

  1. Create a new Swift file named NFCScannerViewController.swift.
  2. Implement the NFC scanning logic using Core NFC.

import UIKit
import CoreNFC

class NFCScannerViewController: UIViewController {
    var nfcSession: NFCNDEFReaderSession?
    var delegate: NFCNDEFReaderSessionDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        startScanning()
    }

    func startScanning() {
        guard NFCNDEFReaderSession.readingAvailable else {
            print("NFC is not available on this device.")
            return
        }

        nfcSession = NFCNDEFReaderSession(delegate: delegate, queue: nil, invalidateAfterFirstRead: false)
        nfcSession?.alertMessage = "Hold your iPhone near the NFC tag to scan."
        nfcSession?.begin()
    }
}

Explanation

  1. ContentView: This SwiftUI view displays a button that starts the NFC scanning process and a text field to show the scanned data.
  2. NFCScannerView: This struct represents a view that uses UIViewControllerRepresentable to present a UIKit view controller in SwiftUI. It initializes an NFC reader session and handles detected NDEF messages.
  3. NFCScannerViewController: This class manages the NFC scanning session. It starts the NFC reader session and ensures that it can read NDEF messages.

Running the App

  1. Build and run the app on a physical device (the simulator does not support NFC).
  2. Tap the “Start Scanning” button and hold the iPhone near an NFC tag.
  3. The scanned data should appear on the screen.

Enhancing the App

To make this app more robust and feature-rich, consider adding:

  • Error handling for NFC operations, including session invalidation and reading errors.
  • UI updates to indicate scanning status (e.g., a loading spinner).
  • Parsing NDEF records to extract specific types of data (e.g., URLs, text records).
  • Writing data to NFC tags (if iOS support for writing is added in the future).

Detailed Explanation

Setting Up Core NFC

Core NFC requires specific setup in your Xcode project and a basic understanding of how NFC works. Core NFC is limited to reading NDEF tags, which is sufficient for many applications, such as scanning product tags, accessing information, and initiating actions based on tag data.

Initializing the NFC Reader Session

An NFC reader session is initiated by creating an NFCNDEFReaderSession object. This session is responsible for handling the detection of NFC tags and reading their contents. When the session begins, the user is prompted to hold their device near an NFC tag.

nfcSession = NFCNDEFReaderSession(delegate: delegate, queue: nil, invalidateAfterFirstRead: false)
nfcSession?.alertMessage = "Hold your iPhone near the NFC tag to scan."
nfcSession?.begin()

  • invalidateAfterFirstRead: If set to true, the session ends after the first tag is read. For continuous reading, set it to false.

Handling NDEF Messages

When an NFC tag is detected, the readerSession(_:didDetectNDEFs:) method is called. This method provides an array of NFCNDEFMessage objects, each containing NFCNDEFRecord objects that represent the data stored on the tag.

func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
    for message in messages {
        for record in message.records {
            if let string = String(data: record.payload, encoding: .utf8) {
                DispatchQueue.main.async {
                    self.scannedData = string
                }
            }
        }
    }
}

Error Handling

Proper error handling ensures a smooth user experience. The readerSession(_:didInvalidateWithError:) method handles session invalidation errors, such as when the session times out or the user cancels it.

func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
    // Handle errors
    print("NFC Session invalidated: \(error.localizedDescription)")
}

SwiftUI Integration

Using UIViewControllerRepresentable, you can seamlessly integrate UIKit-based NFC functionality into a SwiftUI app. This approach allows you to leverage existing UIKit code while building modern SwiftUI interfaces.

struct NFCScannerView: UIViewControllerRepresentable {
    @Binding var scannedData: String

    func makeCoordinator() -> Coordinator {
        Coordinator(scannedData: $scannedData)
    }

    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = NFCScannerViewController()
        viewController.delegate = context.coordinator
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }

    class Coordinator: NSObject, NFCNDEFReaderSessionDelegate {
        @Binding var scannedData: String

        init(scannedData: Binding<String>) {
            _scannedData = scannedData
        }

        func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
            // Handle errors
            print("NFC Session invalidated: \(error.localizedDescription)")
        }

        func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
            for message in messages {
                for record in message.records {
                    if let string = String(data: record.payload, encoding: .utf8) {
                        DispatchQueue.main.async {
                            self.scannedData = string
                        }
                    }
                }
            }
        }
    }
}

In this comprehensive guide, we’ve built a basic NFC reader app using Core NFC in Swift. This app serves as a foundation for more advanced applications that involve reading NFC tags for various purposes.