In this post I would like to point the most important things which were mentioned during Core NFC Enhancements talk on WWDC 2019. Right now I’m starting the new project where NFC knowledge will be very useful for me. Writing this post gave me a kick to learn more about the news regarding NFC from the last Apple conference.

I hope this post will help you to start to use the new NFC features. If you have some comments or you could share some of your experience with NFC, please leave a comment!

Basics

Before we start to write some code let’s talk briefly about NFC itself.

What is NFC?

NFC stands for Near Field Communication. NFC is a set of standards that allow smartphones and other devices to communicate via radio signals when they are held in close proximity.

Near-field communication devices operate at the same frequency (13.56 MHz) as HF RFID (high frequency Radio-Frequency identification) readers and tags. The standards and protocols of the NFC format is based on RFID standards outlined in ISO/IEC 14443, FeliCa, and the basis for parts of ISO/IEC 18092.

NFC is primarily being used for wireless payments with services like Google Pay, Apple Pay, and Samsung Pay and in many other areas like marketing, public transport, helthcare, etc.

Payments logo

What is NDEF?

The NFC Data Exchange Format (NDEF) is a standardized data format that can be used to exchange information between any compatible NFC device and another NFC device or tag. The NDEF format is used to store and exchange information like URIs, plain text, etc.

The news from WWDC

In iOS 13 Apple expanded possibilities of Core NFC. Now iOS is able to read more complex NFC tags, including those that meet ISO 7816 and ISO 15693 standards. Now is enabled to read the data stored on tags associated with passports, driving licences, and other forms of official identification.

The second important thing is that now iPhone apps will be able to write directly to blank tags.

What’s new in the software

What’s new in the hardware

iPhone XS, XS Max and XR (with the new A12 Bionic chip) posses a background feature that allows them to scan NFC tags without the need to open an app.

Before you start

Let’s create a project in Xcode for an app that reads/writes NFC tags and:

1. Add NFC capabilities to your project

capabilities

2. Provide a non-empty string for the NFCReaderUsageDescription key in your app’s info.plist file.

InfoPlist

NDEF Tag writing

Implementation consists of 6 steps:

1. Create session with a NFCNDEFReaderSessionDelegate object

    func beeginScanning() {
    	 // 1
        self.session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead:
            false)
        self.session?.alertMessage = "Hold your iPhone near the item to learn more about it."
        self.session?.begin()
    }

2. Implement optional readerSession(_:didDetect:) method

3. Connect to the NDEF tag

4. Query NDEF status

5. Write NDEF message (with usage of NFCNDEFMessage class)

In our case we used plain text which will be written on tag. This is done by class function wellKnowTypeTextPayload from NFCNDEFPayload class.

6. Invalidate session on completion

 	 // 2
    func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
        
        let tag = tags.first!
        // 3
        session.connect(to: tag) { (error: Error?) in
            if error != nil {
                session.restartPolling()
            }
        }

        // 4
        tag.queryNDEFStatus() { (status: NFCNDEFStatus, capacity: Int, error: Error?) in
            
            if error != nil {
                session.invalidate(errorMessage: "Fail to determine NDEF status.  Please try again.")
                return
            }
            
            let textPayload = NFCNDEFPayload.wellKnowTypeTextPayload(string: "Hello from swifting.io", locale: Locale(identifier: "En"))
            let myMessage = NFCNDEFMessage(records: [textPayload!])
        
            if status == .readOnly {
                session.invalidate(errorMessage: "Tag is not writable.")
            } else if status == .readWrite {
                // 5
                tag.writeNDEF(myMessage) { (error: Error?) in
                    if error != nil {
                        session.invalidate(errorMessage: "Update tag failed. Please try again.")
                    } else {
                        session.alertMessage = "Update success!"
                        // 6
                        session.invalidate()
                    }
                }
            } else {
                session.invalidate(errorMessage: "Tag is not NDEF formatted.")
            }
        }
    }

Full project you can find here: https://github.com/swiftingio/NFCWriter

Native tag reading

ISO7816

About

ISO/IEC 7816 is an international standard related to electronic identification cards with contacts, especially smart cards. The ISO7816 regulates how the data in Smart Cards should be structured. This protocol is used in passports and id documents.

Requirements
  • Application Info.plist must contain list of application identifiers (AIDs)
  • Detected tags callback is invoked if tag is ISO7816 compliant and contains an AID found in the Info.plist (Photo)
  • Include the Near Field Communication Tag Reader Session Formats Entitlement in your app.
  • Provide a non-empty string for the NFCReaderUsageDescription key in your app’s info.plist file.
NFCISO7816Tag

Properties:

  • identifier (UID)
  • historicalBytes
  • applicationData

Methods:

func sendCommand(apdu: NFCISO7816APDU, completionHandler: @escaping (Data, UInt8,
UInt8, Error?) -> Void)
Example

Note: iso14443 Support both Type A & B modulation. NFCTagTypeISO7816Compatible and NFCTagTypeMiFare tags will be discovered.

1. Create session with a NFCTagReaderSessionDelegate object

    func beginScanning() {
        session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self)
        session?.alertMessage = "Hold your iPhone near the ISO7816 tag to begin transaction."
        session?.begin()
    }

2. Implement tagReaderSession(_:didDetect:) method

3. Connect to the ISO7816 tag

4. Send APDU and receive response NFCISO7816APDU

APDU - Application protocol data unit is the communication unit between a reader and a nfc tag. More about APDU message you can find here. Some example instructions (based on https://www.nxp.com/docs/en/data-sheet/MF3ICDX21_41_81_SDS.pdf):

  • INS code ‘A4’ SELECT
  • INS code ‘B0’ READ BINARY
  • INS code ‘D6’ UPDATE BINARY
  • INS code ‘B2’ READ RECORDS
  • INS code ‘E2’ APPEND RECORD
  • INS code ‘84’ GET CHALLENGE
  • INS code ‘88’ INTERNAL AUTHENTICATE
  • INS code ‘82’ EXTERNAL AUTHENTICATE

5. Terminate session with error (optional)

    // 2
    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
    
        if case let NFCTag.iso7816(tag) = tags.first! {
            
            //3
            session.connect(to: tags.first!) { (error: Error?) in
                
                //4
                let myAPDU = NFCISO7816APDU(instructionClass:0, instructionCode:0xB0, p1Parameter:0, p2Parameter:0, data: Data(), expectedResponseLength:16)
                tag.sendCommand(apdu: myAPDU) { (response: Data, sw1: UInt8, sw2: UInt8, error: Error?)
                    in
                    
                    // 5
                    guard error != nil && !(sw1 == 0x90 && sw2 == 0) else {
                        session.invalidate(errorMessage: "Application failure")
                        return
                    }
                }
            }
        }
    }

MIFARE

About

MIFARE logo

The NXP Semiconductors-owned trademark of a series of chips used in contactless smart cards and proximity cards. Typical read/write distance of up to 10 cm (4 inches). Used for example in public transport, access cards, hospitality, loyalty, and micropayment services.

NFCMiFareTag

Properties:

  • identifier (UID)
  • historicalBytes
  • mifareFamily — Ultralight , Plus, DESFire

Methods:

func sendMiFareCommand(commandPacket command: Data, completionHandler: @escaping (Data,
Error?) -> Void)
func sendMiFareISO7816Command(_ apdu: NFCISO7816APDU, completionHandler: @escaping (Data,
UInt8, UInt8, Error?) -> Void)
Example

1. Create session with an NFCTagReaderSessionDelegate object

   func beginScanning() {
        // 1
        session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self)
        session?.alertMessage = "Hold your iPhone near the MIFARE tag to begin transaction."
        session?.begin()
    }

2. Implement tagReaderSession(_:didDetect:) method

    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        // 2
        if case let NFCTag.miFare(tag) = tags.first! {
            session.connect(to: tags.first!) { (error: Error?) in
                
                let apdu = NFCISO7816APDU(instructionClass: 0, instructionCode: 0xB0, p1Parameter: 0, p2Parameter: 0, data: Data(), expectedResponseLength: 16)
                
                tag.sendMiFareISO7816Command(apdu) { (data, sw1, sw2, error) in
                    
                }
            }
        }
    }

ISO15693

About

ISO15693 is an ISO standard for vicinity cards, i.e. cards which can be read from a greater distance as compared with proximity cards. Example of usage:

  • in libraries for tracking books
  • in ski passes
NFCISO15693Tag

Properties:

  • identifier (UID)
  • icManufacturerCode
  • icSerialNumber

Methods:

  • Read single block
  • Read multiple blocks
  • Extended read
  • Lock block and etc. (all methods you can find in the documentation)
Example

1. Create session with a NFCTagReaderSessionDelegate object

   func beginScanning() {
        // 1
        session = NFCTagReaderSession(pollingOption: .iso15693, delegate: self)
        session?.alertMessage = "Hold your iPhone near the ISO15693 tag to begin transaction."
        session?.begin()
    }

2. Implement tagReaderSession(_:didDetect:) method

    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        // 2
        if case let NFCTag.iso15693(tag) = tags.first! {
            session.connect(to: tags.first!) { (error: Error?) in
            
                    tag.readSingleBlock(requestFlags: [.highDataRate, .address], blockNumber:0) { (response:
                        Data, error: Error?) in
                        
                }
            }
        }
    }

FeliCa

About

Felica logo

FeliCa is a contactless smart card system from Sony in Japan. The name stands for Felicity Card. A proximity of 10 centimeters or less is required for communication. FeliCa is used in a wide variety of ways, such as in ticketing systems for public transportations, e-money, and residence door keys.

Requirements
  • Application Info.plist must contain list of system codes (Detected tags callback is invoked if tag contains a system code found in the Info.plist)
NFCFeliCaTag

Properties:

  • currentSystemCode
  • currentIDM

Methods:

func sendFeliCaCommand(commandPacket: Data, completionHandler: @escaping (Data,
Error?) -> Void)
Example

1. Create session with a NFCTagReaderSessionDelegate object

   func beginScanning() {
        // 1
        session = NFCTagReaderSession(pollingOption: .iso18092, delegate: self)
        session?.alertMessage = "Hold your iPhone near the tag to begin transaction."
        session?.begin()
    }

2. Implement tagReaderSession(_:didDetect:) method

    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        // 2
        if case let NFCTag.feliCa(tag) = tags.first! {
            session.connect(to: tags.first!) { (error: Error?) in
                    tag.requestResponse() { (mode: Int, error: Error?) in
                }
            }
        }
    }

Summary

I hope this article was helpful to you to start your journey with NFC. Meanwhile, when I will have more experience with the current project I would like to share some more advanced code snippets. Don’t forget to leave some comment in the case of any insight from your side!

External sources