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.
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 iphone nfc tags applications where 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.
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.
Hi,
It’s possible send APDU request?
As far as we can tell, this is all Read-only for now
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.
Android works in background also, i don’t know for NFC in iOS. The next week probably I’ll try a new job with this technology on iOS.
okay perfect, do you have any weblog or website that I can follow your results?
Stupid question: is it possible now to get photos (for example) from other phone via NFC, like Android phones can?
So would this allow us to use nfc to lets say open a door?
Only if the door is a smart door that the app can interact with through an API or bluetooth type of thing.
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.
Maybe you forgot to set the delegate?
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.
Hmm, I’m not sure. Is there anything logging in to the console? Also, are you sure the callback isn’t being triggered?
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.
https://vimeo.com/221143249
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. https://vimeo.com/221143249
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..
it is possible that your transportation card doesn’t use NDEF, and iOS will only see NDEF, not other NFC data types. Try reading the card with an Android device, they are much better at NFC than iOS
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?
Look at the record.payload property
Hi – thanks for the reply. I have looked at the apple docs and what is on the net, but the payload does not seem to have any properties that bring back the data element in any readable format – but sure I am missing something.
I was – found the Apple sample from the video mentioned on this post and realised from that how to display the values 🙂 Happy days and thanks for a very helpful post and comments
Hello, where i can get or buy (or write by myself) the tag with predefined NDEF records?
Hi – we sell all kinds of NFC items (zipnfc.com) and you can use Android apps like NXP TagWriter to create NDEF records. Based on the help with this post we managed to get a beta app going for IOS tag reading and launching:-) https://vimeo.com/224768905
Hi Jameson,
thank you for your article, great introduction into scanning NFC tags on the iPhone. I have followed your video/code to the letter but am getting a ‘Feature not supported’ error when i try to scan a code. do you have any idea why this could be?
Thanks,
James
Probably your device does not support it. It is only the 7 and up.
Thanks for the tutorial. It helped me. CoreNFC SDK does not support parsing NFC NDEF message payload. I created an open source library VYNFCKit to parse payload. You can get it at https://github.com/vinceyuan/VYNFCKit
Hello Jameson Quave i have problem with NFC on my app .the xcode doen’t give me permssion to run my app on a real iphone… error tell me you need to developer account by buy from apple …is there a way to run my app on my real iphone without buy developer account
You will need a developer account
Am looking for the same topic here, for the purpose of learnng i want to do an app that can send from one iPhone to another Texts or data using NFC. Any updates on this issue. I have checked apple new NFC yet is it possible to do a peer to peer NFC between iphones without the need of using TAGSS …
And is there anyway to bypass the tags since i do not have tags how may i test this ?!
Please advice