Using NFC With iOS in React Native

A look at react-native-nfc-manager

Tamas Szikszai
Nov 10, 2019 · 6 min read
Image for post
Image for post

With iOS 13, Apple finally opened the SDK to write data on some of the most popular NFC chips.

Thankfully one of the most popular (and to my knowledge, only) NFC React Native libraries, react-native-nfc-manager, also added (some) support to do this. Let’s see how it works in practice!

TL;DR 1: Too lazy to read? Watch the video:

TL;DR 2: Just want the code? Here is the code!

Installing the Library

Installation is actually super simple. First, we need to add the module via npm.

npm i --save react-native-nfc-manager

For Android, that’s it. For iOS, you have to do a couple of extra steps. First of all, run a pod install:

cd ios && pod install && cd ..

Then, you have to add the following lines to your info.plist (in ios/YourProject):

<key>NFCReaderUsageDescription</key>
<string>YOUR_PRIVACY_DESCRIPTION</string>

You also need to add the “Near Field Communication Tag Reading” capability to your app:

Image for post
Image for post

Scaffolding

First, let’s make some buttons and scaffolding code:

import React, { Component } from 'react';
import {
SafeAreaView,
StyleSheet,
View,
Text,
TextInput,
Alert,
Platform,
TouchableOpacity
} from 'react-native';
import NfcManager, { NfcTech } from 'react-native-nfc-manager';
class App extends Component {
constructor(props){
super(props);
this.state = {
log: "Ready...",
text: ""
}
}
componentDidMount(){
NfcManager.start();
}
componentWillUnMount(){
this.cleanUp();
}
cleanUp = () => {
NfcManager.cancelTechnologyRequest().catch(() => 0);
}
onChangeText = (text) => {
this.setState({
text
})
}
writeData = async () => {

}
readData = async () => {

}
render(){
return (
<SafeAreaView style={styles.container}>
<TextInput
style={styles.textInput}
onChangeText={this.onChangeText}
autoCompleteType="off"
autoCapitalize="none"
autoCorrect={false}
placeholderTextColor="#888888"
placeholder="Enter text here" />
<TouchableOpacity
onPress={this.writeData}
style={styles.buttonWrite}>
<Text style={styles.buttonText}>Write</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.readData}
style={styles.buttonRead}>
<Text style={styles.buttonText}>Read</Text>
</TouchableOpacity>
<View style={styles.log}>
<Text>{this.state.log}</Text>
</View>
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center'
},
textInput: {
marginLeft: 20,
marginRight: 20,
height: 50,
marginBottom: 10,
textAlign:'center',
color: 'black'
},
buttonWrite: {
marginLeft: 20,
marginRight: 20,
height: 50,
marginBottom: 10,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
backgroundColor: '#9D2235'
},
buttonRead: {
marginLeft: 20,
marginRight: 20,
height: 50,
marginBottom: 10,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
backgroundColor: '#006C5B'
},
buttonText: {
color: 'white'
},
log: {
marginTop: 30,
height: 50,
alignItems: 'center',
justifyContent: 'center',
}
});
export default App;

There is not much to explain here.

We’ve set up an input field that will contain the text we want to write to our tag. Also two buttons, one for writing and one for reading. Finally, a text field for logging stuff. Pretty basic…

Writing Data

With all honesty, before writing this article, I didn’t know that there are different NFC chips. I simply ordered the cheapest one from Amazon, which happened to be a Mifare Ultralight chipset one.

Unluckily, the library’s support for this chipset on iOS is pretty basic: all you can do is send commands to the chip.

My first instinct was to order a new set of tags with better support. But then I started Googling things and found the documentation for Mifare Ultralight.

On page 41, the documentation describes the commands you need to send to write data. Here is how it looks in React Native:

writeData = async () => {
if (!this.state.text){
Alert.alert("Enter some text");
return;
}
try {
let tech = Platform.OS === 'ios' ? NfcTech.MifareIOS : NfcTech.NfcA;
let resp = await NfcManager.requestTechnology(tech, {
alertMessage: "Ready for magic"
});
let cmd = Platform.OS === 'ios' ? NfcManager.sendMifareCommandIOS : NfcManager.transceive;
let text = this.state.text;
let fullLength = text.length + 7;
let payloadLength = text.length + 3;
resp = await cmd([0xA2, 0x04, 0x03, fullLength, 0xD1, 0x01]);
resp = await cmd([0xA2, 0x05, payloadLength, 0x54, 0x02, 0x65]) // T enYourPayload
let currentPage = 6;
let currentPayload = [0xA2, currentPage, 0x6E]; // n
for(let i=0; i<text.length; i++){
currentPayload.push(text.charCodeAt(i));
if (currentPayload.length == 6){
resp = await cmd(currentPayload);
currentPage += 1;
currentPayload = [0xA2, currentPage]
}
}
currentPayload.push(254);
while(currentPayload.length < 6){
currentPayload.push(0);
}
resp = await cmd(currentPayload);
this.setState({
log: resp.toString() === "10" ? "Success" : resp.toString()
})
//sendMifareCommandIOS
} catch(err){
this.setState({
log: err.toString()
})
this.cleanUp();
}
}

OK, don’t get scared, it’s actually pretty simple. Let’s break it down.

let resp = await NfcManager.requestTechnology(tech, {
alertMessage: "Ready for magic"
});

These three lines tell the engine that you are trying to communicate with a Mifare Ultralight chip. iOS will automatically render the standard UI and the execution will wait until such a device gets in close proximity of the NFC reader.

At this point, the chip is ready to accept commands. On iOS, you can send commands with NfcManager.sendMifareCommandIOS, which accepts a list of bytes.

Based on the documentation, all write commands should start with 0xA2 followed by the address of the page you are writing. On my chip, all pages can contain four bytes and their addresses are between 0x02 and 0x2C.

0x02 and 0x03 are actually reserved, so the first page you want to write is 0x04.

We are going to write a text-type record, which should follow a certain pattern:

  1. The first byte is 0x03.
  2. The second byte should be the length of the full payload (it will be your text’s length + 7).
  3. The third byte is 0xD1.
  4. The fourth byte is 0x01.
  5. The fifth byte is the length of the record (in case of a text record, it will be the text’s length + 3).
  6. 0x54 -> the character “T”.
  7. 0x02
  8. 0x65 -> the character “e”.
  9. 0x6E-> the character “n”.
  10. Your text.
  11. 0xFE to close the record.

This part of the code takes care of the above:

let text = this.state.text;
let fullLength = text.length + 7;
let payloadLength = text.length + 3;
resp = await cmd([0xA2, 0x04, 0x03, fullLength, 0xD1, 0x01]);
resp = await cmd([0xA2, 0x05, payloadLength, 0x54, 0x02, 0x65]) // T enYourPayload
let currentPage = 6;
let currentPayload = [0xA2, currentPage, 0x6E]; // n
for(let i=0; i<text.length; i++){
currentPayload.push(text.charCodeAt(i));
if (currentPayload.length == 6){
resp = await cmd(currentPayload);
currentPage += 1;
currentPayload = [0xA2, currentPage]
}
}
currentPayload.push(254);
while(currentPayload.length < 6){
currentPayload.push(0);
}
resp = await cmd(currentPayload);

Reading Data

Reading data is actually a bit simpler. Here is the implemented function:

readData = async () => {
try {
let tech = Platform.OS === 'ios' ? NfcTech.MifareIOS : NfcTech.NfcA;
let resp = await NfcManager.requestTechnology(tech, {
alertMessage: "Ready for magic"
});
let cmd = Platform.OS === 'ios' ? NfcManager.sendMifareCommandIOS : NfcManager.transceive;resp = await cmd([0x3A, 4, 4])
let payloadLength = parseInt(resp.toString().split(",")[1]);
let payloadPages = Math.ceil(payloadLength / 4);
let startPage = 5;
let endPage = startPage + payloadPages - 1;
resp = await cmd([0x3A, startPage, endPage]);
let bytes = resp.toString().split(",");
let text = ""
for(let i=0; i<bytes.length; i++){
if (i<5){
continue;
}
if (parseInt(bytes[i]) === 254){
break;
}
text = text + String.fromCharCode(parseInt(bytes[i]));
}
this.setState({
log: text
})
} catch(err){
this.setState({
log: err.toString()
})
this.cleanUp();
}
}

After requesting the technology, we can start sending commands. Page 39 of the documentation explains the FAST_READ command, which has a very simple interface:

  1. The first byte of the request is 0x3A.
  2. The second byte is the first page you want to read.
  3. The third byte is the last page you want to read.

In our implementation, we first read the third page, which contains the full payload’s length. Based on the payload’s length, we can calculate how many pages we need to request.

From this point, we will just ignore the text record’s header (i<5) and keep reading characters until the 0xFE terminator.

Conclusion

And that’s all. This is, of course, just a proof of concept. You can do a ton of stuff with NFC technology.

The library for iOS is in pretty early stages right now but the repository is very active, so I’m sure a much better API will be released soon.

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store