Core NFC Tutorial for NFC on iOS Devices

 

With the release of iOS 11, for the first time third-party developers are able to use the NFC reader on devices iPhone 7 and higher. This could be used for passing along identification information and a whole host of other data exchange applications from door locks to subway passes.

The technology used on iOS 11 is called Core NFC, and I’m going to go over how to use it in this tutorial in Swift 4.

Core NFC Device communications on iOS

Because Core NFC is currently read-only, functionality such as contactless payments will not be possible out of the box, there are however other applications we can use the Core NFC reading capabilities for. So let me show you how it works.

The first step to work with NFC is to enable it for your App ID in the Apple Developer Center. Create a new App ID and enable “NFC Tag Reading” as a capability for the app.

NFC Tag Reading App Service

After you do this, I recommend creating a development/distribution provisioning profile specifically for this app ID, so that the NFC reading capability will be present when you try to build.

Next, in your Xcode project add the entitlement to your projectName.entitlements file. You’ll need to right click the file and select “Open As Source Code” to manually enter this key as shown:

<key>com.apple.developer.nfc.readersession.formats</key>
<array>
  <string>NDEF</string>
</array>

If you do not have an entitlements file, you can manually create one and point to it in the settings of the project. Under “Build Settings” look for the record “Code Signing Entitlements”, and punch in the relative path to your entitlements file. In my case it’s “CoreNFC-Tutorial/CoreNFC-Tutorial.entitlements” because my project files are inside a sub-folder called “CoreNFC-Tutorial”.

Next, we need to add the usage string to your Xcode project. Open your Info.plist file and add a new record, start typing and allow it to autocomplete “Privacy – NFC Scan Usage Description”. This message will be shown to users when NFC is used, so for the value enter something useful for the user such as “NFC is needed to unlock doors.”.

Next, in our code we want to import the CoreNFC module.

import CoreNFC

Note: Core NFC is completely unavailable on the iOS simulator, and even importing the module will fail. So Core NFC is for device-only testing!

I created an NFCHelper.swift to store all my NFC-related calls, and put everything in an NFCHelper class. In the init method of the class I created a session. Core NFC requires you utilize the NFCNDEFReaderSession class in order to start listening for NFC communications. (Note the class NFCReaderSession is abstract and should not be used directly)

class NFCHelper {
  init() {
    let session =
      NFCNDEFReaderSession(delegate: self,
                           queue: nil,
                           invalidateAfterFirstRead: true)
    session.begin()
  }
}

Here we create the session, and pass in a nil dispatch queue. Doing so will cause NFCNDEFReaderSession to automatically create a serial dispatch queue.

Creating a session, we can specify a delegate in our NFCNDEFReaderSession class. I would like to use the NFCHelper class as the delegate, so we must first adhere to the delegate protocol, NFCNDEFReaderSessionDelegate. This delegate is based on an Objective-C object, so we must first also adhere to NSObject. NFCNDEFReaderSessionDelegate has two delegate methods we must implement:

func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error)
 
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage])

These two callbacks are called when an NFC session has a validation error, or when NFC activity is detected. How we use the messages will depend on your specific application, but everything you need to know will be sent through the didDetectNDEFs callback in the form of records inside of messages. To get started, you can log the results of each message in a loop. These are NFCNDEFPayload objects, which contain an identifier, payload, type, and typeNameFormat.

func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
  print("Did detect NDEFs.")
  // Loop through all messages
  for message in messages {
    for record in message.records {
      print(record.identifier)
      print(record.payload)
      print(record.type)
      print(record.typeNameFormat)
    }
  }
}

To clean this up a little bit so I can actually integrate with the front-end of the app, I created a callback specific for my application. You may want to do something similar. I added a callback variable that the implementing view can work with:

I call this when I get a payload from an NFC tag, or an error:

class NFCHelper: NSObject, NFCNDEFReaderSessionDelegate {
  ...
  var onNFCResult: ((Bool, String) -> ())?
  ...
}

I also broke out my init function to open up a session using a method, so I can restart the session from a button on my ViewController. My final code for my NFCHelper.swift file is as follows:

//
//  NFCHelper.swift
//  CoreNFC-Tutorial
//
//  Created by Jameson Quave on 6/6/17.
//  Copyright © 2017 Jameson Quave. All rights reserved.
//
 
import Foundation
import CoreNFC
 
class NFCHelper: NSObject, NFCNDEFReaderSessionDelegate {
 
  var onNFCResult: ((Bool, String) -> ())?
 
  func restartSession() {
    let session =
    NFCNDEFReaderSession(delegate: self,
                       queue: nil,
                       invalidateAfterFirstRead: true)
    session.begin()
  }
  // MARK: NFCNDEFReaderSessionDelegate
  func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
    guard let onNFCResult = onNFCResult else {
      return
    }
    onNFCResult(false, error.localizedDescription)
  }
  func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
    guard let onNFCResult = onNFCResult else {
      return
    }
    for message in messages {
      for record in message.records {
        if(record.payload.count > 0) {
          if let payloadString = String.init(data: record.payload, encoding: .utf8) {
              onNFCResult(true, payloadString)
          }
        }
      }
    }
  }
 
}

I also set up a simple UI in my ViewController to demonstrate usage of this class:

//
//  ViewController.swift
//  CoreNFC-Tutorial
//
//  Created by Jameson Quave on 6/6/17.
//  Copyright © 2017 Jameson Quave. All rights reserved.
//
 
import UIKit
 
class ViewController: UIViewController {
  var helper: NFCHelper?
  var payloadLabel: UILabel!
  var payloadText = ""
  override func viewDidLoad() {
    super.viewDidLoad()
    // Add a detect button
    let button = UIButton(type: .system)
    button.setTitle("Read NFC", for: .normal)
    button.titleLabel?.font = UIFont(name: "Helvetica", size: 28.0)
    button.isEnabled = true
    button.addTarget(self, action: #selector(didTapReadNFC), for: .touchUpInside)
    button.frame = CGRect(x: 60, y: 200, width: self.view.bounds.width - 120, height: 80)
    self.view.addSubview(button)
    // Add a label to display the payload in
    payloadLabel = UILabel(frame: button.frame.offsetBy(dx: 0, dy: 220))
    payloadLabel.text = "Press Read to see payload data."
    payloadLabel.numberOfLines = 100
    self.view.addSubview(payloadLabel)
  }
  // Called by NFCHelper when a tag is read, or fails to read
  func onNFCResult(success: Bool, message: String) {
    if success {
      payloadText = "\(payloadText)\n\(message)"
    }
    else {
      payloadText = "\(payloadText)\n\(message)"
    }
    // Update UI on main thread
    DispatchQueue.main.async {
      self.payloadLabel.text = self.payloadText
    }
 
  }
  // Called when user taps Read NFC Button
  @objc func didTapReadNFC() {
    if helper == nil {
      helper = NFCHelper()
      helper?.onNFCResult = self.onNFCResult(success:message:)
    }
    payloadText = ""
    helper?.restartSession()
  }
}

From here you can integrate the rest of your app with your intended use-case. Whether it’s to identify visitors to an event, check out the stats on your Amiibo, or even process payments, the Core NFC API from Apple has finally opened up the possibilities for NFC integration on these new devices. What kind of NFC product are you working on? Let me know at jquave@gmail.com.

Full Source Code

Video Tutorial (YouTube)

Follow me on Twitter


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.

Subscribe via RSS

21 thoughts on “Core NFC Tutorial for NFC on iOS Devices

  1. does anyone knows for notification do we need to unlock our cellphone to get that? or does it support local notification for locked cellphones as well? because beacons can turn the phone on(screen) when you enter the pre-determined region.

  2. I implemented the same thing but but it says “Ready to Scan” my call back is not called when I tap my NFC card. Do you know what could go wrong? Thanks.

      • Thanks for your response. I did set the delegate by calling this line: “self.nfcSession = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)”.
        Then I tagged my NFC transportation card on my iPhone 7. but nothing happened.

          • We sell NFC tags so have tried all sorts of NFC Tags shapes an sizes and at this stage I have found the iPhone is so unreliable in terms of picking up the tag and had to move the tag around bottom and even on top of the top of the phone to get anything. I have not tried cards as yet but will at some point and report back.

            The other thing is the tag does have to have data on it – like a text record rather than being blank in my experience and this was confirmed by other posts and comments. So you need an Android phone to do that:-)

            • iPhone does read NFC cards, but like the stickers it takes a few times before the card registers. I wonder if the NFC chip in the iPhone is a lower powered version than the Android chips used, but early days so might just be set-up/configuration I guess. If anyone else has had more luck with scanning all the time it would be great to know the positioning against the phone. We are using the iPhone 7+ by the way!

              • Hi Chris, we use encrypted Mifare Classic and Ultralight C chips in our business. I’m unable to get the iPhone to pick up anything no matter what I do… Is there anything special I might be missing? Have you figured anything out?

                • We have tried all types of NTAG and works fine so would expect Utralight C to be OK. Not tried a tag with encryption and not sure the library supports that at the moment to be honest. One thing we have found is it can stop reading tags and sometime have to reboot the iPhone to kick start and something reload from the debugger. One thing is for sure the tag has to have a NDEF record on it to read anything. Does not seem to read empty tags. We did do this video of the iPhone working really well.

                • Hi – yes we have tried all sorts of NTAG cards and they work perfectly. Would have thought Ultralight C would work ok, but not sure there is any visible support for encrypted in the library as it stands but might be wrong.. Here is a video of our demo app we released.

                  You definitely have to have a NDEF record for it to read as far as we can tell or you get nothing from the scan currently..

  3. Hi – thanks for this – it was very helpful for me to get a skeleton app up and running and pleased to say I can get a result from scanning an NFC tag on my iPhone7+. But the code above display the length of the payload which is correct, but I need to get the payload contents. Any ideas on how you would extend this code to so that?

Comment