Getting Started with Core NFC

In this tutorial, you’ll learn how to use CoreNFC to connect wirelessly to other devices or NFC tags. By Andy Pereira.

4.3 (3) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Writing Custom Data Instead of Strings

At this point, you’re ready to convert the app from writing strings to a tag to writing custom data to a tag. Add the following to the Utilities extension:

private func createLocation(_ location: Location, tag: NFCNDEFTag) {
  read(tag: tag) { _ in
    self.updateLocation(location, tag: tag)
  }
}

This is your new function for creating a tag with a location. You can see that it uses the new read(tag:alsertMessage:readCompletion:) to start the process and calls a new function for updating a location on a tag, and also a new updateLocation(_:tag:) method which you’ll implement momentarily.

Since you’re replacing the way you write location information to a tag, remove createLocation(name:with:) at the beginning of the NFCUtility extension, as it is no longer needed. Also, update your code in readerSession(_:didDetect:) from this:

case (.readWrite, .setupLocation(let locationName)):
  self.createLocation(name: locationName, with: tag)

…to the following:

case (.readWrite, .setupLocation(let locationName)):
  self.createLocation(Location(name: locationName), tag: tag)

Next, add this method after createLocation(_:tag:):

private func updateLocation(
  _ location: Location,
  withVisitor visitor: Visitor? = nil,
  tag: NFCNDEFTag
) {
  // 1
  var alertMessage = "Successfully setup location."
  var tempLocation = location
  
  // 2
  let jsonEncoder = JSONEncoder()
  guard let customData = try? jsonEncoder.encode(tempLocation) else {
    self.handleError(NFCError.invalidated(message: "Bad data"))
    return
  }
  // 3
  let payload = NFCNDEFPayload(
    format: .unknown,
    type: Data(),
    identifier: Data(),
    payload: customData)
  // 4
  let message = NFCNDEFMessage(records: [payload])
}

Here’s what you’re doing in the code above:

  1. Create a default alert message and temporary location. You’ll come back to these later.
  2. Encode the Location struct passed in to the function. This will convert the model to raw Data. This is important, as it’s the way you write any custom type to an NFC tag.
  3. Create a payload that can handle your data. However, you now use unknown as the format. When doing this, you must set the type and identifier to empty Data, while the payload argument hosts the actual decoded model.
  4. Add the payload to a newly-created message.

Overall, this shouldn’t seem too different than when you saved a string to a tag — you’re just adding an extra step to convert a Swift data type to something a tag understands.

Checking Tag Capacity

To finish writing the data to the tag, add the next block of code within updateLocation(_:withVisitor:tag):

tag.queryNDEFStatus { _, capacity, _ in
  // 1
  guard message.length <= capacity else {
    self.handleError(NFCError.invalidPayloadSize)
    return
  }

  // 2
  tag.writeNDEF(message) { error in
    if let error = error {
      self.handleError(error)
      return
    }
    
    if self.completion != nil {
      self.read(tag: tag, alertMessage: alertMessage)
    }
  }
}

The closure above attempts to query the current NDEF status and then:

  1. Makes sure the device has enough storage to to store the location. Remember, NFC tags often have an extremely limited storage capacity compared to devices you might be familiar with.
  2. Write the message to the tag.

Build and run and set up a location as you did above. You can use the same tag from before if you wish, as your new code will overwrite any previously saved data.

Reading Your Custom Data

At this point, if you try to read your tag, you'll get an error. The data saved is no longer a well-known type. To fix this, replace the following code in readerSession(_:didDetect:):

case (.readWrite, .readLocation):
  self.readLocation(from: tag)

...with this:

case (.readWrite, .readLocation):
  self.read(tag: tag)

Build and run and scan your tag. Because you're calling read(tag:alertMessage:readCompletion:) without any completion blocks, it will decode the data found in the message's first record.

Modifying Content

The last requirement for this app is to save a log of people that have visited this location. Your app has an unused feature already present in the UI that allows users to enter their name and add it to a tag. The work you've done so far will make the rest of the setup trivial. You can already read and write data to a tag, so modifying it should be a breeze for you.

In NFCUtility.swift, add this code to updateLocation(_:withVisitor:tag:) just after you create tempLocation:

if let visitor = visitor {
  tempLocation.visitors.append(visitor)
  alertMessage = "Successfully added visitor."
}

In the code above, you check to see if a visitor was provided. If so, you add it to the location's visitors array.

Next, add the following method to the Utilities extension:

private func addVisitor(_ visitor: Visitor, tag: NFCNDEFTag) {
  read(tag: tag) { message in
    guard 
      let message = try? message.get(),
      let record = message.records.first,
      let location = try? JSONDecoder()
        .decode(Location.self, from: record.payload) 
      else {
        return
    }

    self.updateLocation(location, withVisitor: visitor, tag: tag)
  }
}

This new method will read a tag, get the message from it, and attempt to decode the Location on it.

Next, in readerSession(_:didDetect:), add a new case to your switch statement:

case (.readWrite, .addVisitor(let visitorName)):
  self.addVisitor(Visitor(name: visitorName), tag: tag)

If the user specifically wants to add a visitor, you'll call the function you added in the previous step.

All that remains is to update VisitorView.swift. In visitorSection, replace the following code:

Button(action: {
}) {
  Text("Add To Tag…")
}
.disabled(visitorName.isEmpty)

With this one:

Button(action: {
  NFCUtility
    .performAction(.addVisitor(visitorName: self.visitorName)) { location in
      self.locationModel = try? location.get()
      self.visitorName = ""
    }
}) {
  Text("Add To Tag…")
}
.disabled(visitorName.isEmpty)

Build and run, then go to the Visitors tab. Enter your name, then select Add To Tag…. After scanning, you'll see the updated location with a list of visitors found on the tag.

A scanned location tag showing location name an a list of visitors

Where to Go From Here?

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

Now you should be familiar with the basics of Core NFC. There's still a lot of things to do with the framework that were not even mentioned in this tutorial. For example, you can add background reading of tags, which can provide a way for your users to interact with your app without having to open it. If you've used Apple's Shortcuts app to automate your smart home devices, this should seem familiar to you. You can find out more about doing this here: Adding Support for Background Tag Reading.

To learn more, checkout some of these great resources:

Apple's Core NFC Documentation is your go-to resource for everything Apple supports within the NFC spec.

The NFC Forum Homepage is the place for all the information you need about NFC in general, and the specifications it defines.

If you have any questions about this tutorial, please join in the forum discussion below!