RORK LABJP
IOS27 — iOS 27 and iPadOS 27 ship on September 14, three days out. The ground always shifts under app builders in the days right after a releaseWAIT — Standard Rork waits on Expo and React Native to catch up. Rork Max can follow as soon as Xcode and the SDK are ready. Different kinds of waitingFOLD — The folding iPhone Duo starts at $1,999. A new screen shape is the first place a generated layout tends to come apartSTACK — Standard Rork emits React Native through Expo; Rork Max emits native Swift. A great many write-ups still conflate the twoPRICE — Standard tiers run Free, Junior at $25, Middle at $50 and Senior at $100. Rork Max spans $200 to $1,800 a month depending on tierSOURCE — Funding figures differ across secondary coverage right now. If you cannot verify a number at the source, leave it outIOS27 — iOS 27 and iPadOS 27 ship on September 14, three days out. The ground always shifts under app builders in the days right after a releaseWAIT — Standard Rork waits on Expo and React Native to catch up. Rork Max can follow as soon as Xcode and the SDK are ready. Different kinds of waitingFOLD — The folding iPhone Duo starts at $1,999. A new screen shape is the first place a generated layout tends to come apartSTACK — Standard Rork emits React Native through Expo; Rork Max emits native Swift. A great many write-ups still conflate the twoPRICE — Standard tiers run Free, Junior at $25, Middle at $50 and Senior at $100. Rork Max spans $200 to $1,800 a month depending on tierSOURCE — Funding figures differ across secondary coverage right now. If you cannot verify a number at the source, leave it out
Articles/Dev Tools
Dev Tools/2026-06-28Advanced

Write to NFC Tags with Rork Max: Add a Step Beyond Read-Only

The NFC apps Rork Max generates usually start with reading. But the experience really changes when you add writing. Here is how to write NDEF to a tag with CoreNFC, plus the pitfalls you can only catch on a real device.

Rork Max233NFC2CoreNFCSwift47indie developer39

After you build a "tap a tag and a URL opens" app with Rork Max, the next thing you usually want is the reverse: writing information to a tag from the app. A visit stamp, a settings handoff, a unique ID tied to an exhibit. While it only reads, the app is a convenient entrance; the moment you add writing, it turns into a tool that links your app to physical objects and the world. As an indie developer, I was struck by the weight of that single step when I tried a "tap to transfer settings" trick for an in-store display of one of my wallpaper apps.

But asking Rork Max in plain English for "a feature to write to NFC tags" doesn't guarantee the code runs as-is on a device. Writing is a notch more delicate than reading, can't be checked in the simulator at all, and depends on the tag's capacity and state. Below, building on the Swift that Rork Max generates, I lay out the essentials of writing NDEF to a tag and the pitfalls only a real-device test reveals.

Design reading and writing as different things

In CoreNFC, both reading and writing enter through NFCNDEFReaderSession. The name makes it look read-only, but calling writeNDEF on a connected tag makes it a write. There's a design fork here. Reading ends once a message arrives, but writing must go through several stages: check the tag state, check capacity, perform the write, and verify.

When you ask Rork Max to "read," it returns straightforward code, but for "write" it sometimes produces a version that skips the verification stage. Skip verification and you can't tell when a write you thought succeeded actually failed. So from the start I think of writing in four stages: status check, capacity check, write, and read-back verification.

Build the NDEF and write it to the tag

First, assemble the payload as an NFCNDEFMessage. Writing one URL is the most practical case, so let's center on that.

import CoreNFC
 
final class NFCWriter: NSObject, NFCNDEFReaderSessionDelegate {
    private var session: NFCNDEFReaderSession?
    private var payload: NFCNDEFMessage?
 
    func write(urlString: String) {
        guard NFCNDEFReaderSession.readingAvailable else {
            print("This device does not support NFC")
            return
        }
        guard let url = URL(string: urlString),
              let record = NFCNDEFPayload.wellKnownTypeURIPayload(url: url) else {
            print("Could not convert the URL into an NDEF record")
            return
        }
        payload = NFCNDEFMessage(records: [record])
 
        session = NFCNDEFReaderSession(delegate: self,
                                       queue: nil,
                                       invalidateAfterFirstRead: false)
        session?.alertMessage = "Hold your iPhone near the tag to write"
        session?.begin()
    }
 
    // Tag-detected callback. Check state and capacity here before writing
    func readerSession(_ session: NFCNDEFReaderSession,
                       didDetect tags: [NFCNDEFTag]) {
        guard let tag = tags.first else { return }
        if tags.count > 1 {
            session.alertMessage = "Multiple tags detected. Please present only one."
            session.restartPolling()
            return
        }
        session.connect(to: tag) { error in
            if let error = error {
                session.invalidate(errorMessage: "Connect failed: \(error.localizedDescription)")
                return
            }
            self.queryAndWrite(tag: tag, session: session)
        }
    }
 
    func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {}
    func readerSession(_ session: NFCNDEFReaderSession,
                       didInvalidateWithError error: Error) {}
    func readerSession(_ session: NFCNDEFReaderSession,
                       didDetectNDEFs messages: [NFCNDEFMessage]) {}
}

Using wellKnownTypeURIPayload(url:) lets CoreNFC handle URI identifier shortening (the scheme that compresses prefixes like https://www. into a single byte). It's safer than hand-assembling the TNF and type, and it doesn't waste the tag's limited capacity. I set invalidateAfterFirstRead to false because writing needs to keep working after detection; leave it true and the session closes the instant the tag is touched, so you can't write.

Confirm state and capacity before writing

Here's the body of the write. Use queryNDEFStatus first to confirm whether the tag is writable and how much capacity it has.

extension NFCWriter {
    func queryAndWrite(tag: NFCNDEFTag, session: NFCNDEFReaderSession) {
        tag.queryNDEFStatus { status, capacity, error in
            if let error = error {
                session.invalidate(errorMessage: "Status query failed: \(error.localizedDescription)")
                return
            }
            guard let message = self.payload else {
                session.invalidate(errorMessage: "No data to write")
                return
            }
 
            switch status {
            case .notSupported:
                session.invalidate(errorMessage: "This tag does not support NDEF")
            case .readOnly:
                session.invalidate(errorMessage: "This tag is write-protected")
            case .readWrite:
                // Capacity check. Reject overflow up front, not at write time
                let needed = message.length
                if needed > capacity {
                    session.invalidate(
                        errorMessage: "Insufficient capacity: need \(needed) bytes / only \(capacity) available")
                    return
                }
                tag.writeNDEF(message) { error in
                    if let error = error {
                        session.invalidate(errorMessage: "Write failed: \(error.localizedDescription)")
                    } else {
                        session.alertMessage = "Write complete"
                        session.invalidate()
                    }
                }
            @unknown default:
                session.invalidate(errorMessage: "Unknown tag state")
            }
        }
    }
}

The length property of NFCNDEFMessage is the number of bytes actually written to the tag. Comparing it against the capacity from queryNDEFStatus up front lets you reject an overflow before the moment of writing rather than during it. A cheap NTAG213 holds only around 144 bytes, so a long URL with query parameters overflows easily. For store use I prepared a separate short URL and wrote only the short one to the tag.

Note that Info.plist must contain NFCReaderUsageDescription, and writing also requires enabling Near Field Communication Tag Reading under Capabilities and the NDEF format entitlement. If these are missing in the Rork Max project settings, you get a confusing failure: the build passes, but on a device the session closes immediately.

Anticipate the failures that only appear on a device

NFC doesn't work in the simulator at all. That means a write feature assumes real-device testing from the moment you write it. This is where Rork Companion helped. Even before preparing a paid Apple Developer account, you can load the app onto a real iPhone and confirm writing to a tag, which lets you iterate more. The more a feature deals with physical tags, like writing, the more this "try it on a device right away" value matters.

The failures worth getting ahead of on a device collapse roughly into these.

SymptomLikely causeFix
Session ends instantly on tapMissing entitlement / Info.plist settingCheck NFC Capabilities and NFCReaderUsageDescription
Fails on insufficient capacityURL too long / small-capacity tagUse a short URL / choose NTAG215 or larger
Writes but disappears laterNo write-lock appliedImplement read-only locking once operations stabilize
Some tags occasionally won't writeTag not formatted/initializedInsert an NDEF format stage beforehand

That last one — "not formatted" — can crop up even in brand-new tags, and without read-back verification it becomes the worst experience: "thought it succeeded, but empty." I recommend always adding, in production, a verification stage that reads the tag again in the same session and confirms the written URL comes back.

Where to go after adding writing

Extending from reading to writing changes the app's role from "receiving information" to "placing information into the world." The core of the implementation is finishing the status and capacity checks first, then reading back to verify after the write. Keep those four stages intact and you only need to add the verification stage to Rork Max's output to get a write feature that holds up in practice.

As a next step, confirm a minimal version that writes a single short URL on a device via Companion, and keep logs of capacity and verification. With those logs you can decide which NTAG type to choose and how short to make the URL from measurements rather than guesses. I hope it gives a first foothold to anyone taking on a feature that links software to physical objects, the same way I have in indie development.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-07-13
Losing HealthKit Data on Incremental Sync — Designing HKQueryAnchor Persistence
When step or sleep data double-counts or goes missing on incremental HealthKit sync, the root cause is usually HKQueryAnchor persistence. Here is a working Swift design that handles newAnchor and deletedObjects correctly and stays consistent across reinstalls and background updates.
Dev Tools2026-07-18
Your AR Furniture Is Gone by Morning — Persisting Placements with ARWorldMap
AR apps generated by Rork Max lose every placed object on relaunch. Here is the design that fixes it: when to save an ARWorldMap, how to encode custom anchors, how to handle the relocalization wait, and what to do when relocalization simply never lands.
Dev Tools2026-07-17
Killing the Export Compliance Prompt in Rork Builds for Good
Every Rork and Rork Max build lands in App Store Connect with a Missing Compliance warning. Here is how to decide whether you qualify for the exemption, and how to set it once in app.json or Info.plist so the question never returns.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links