I'm working on an app that reads an NFC tag and uses that data to select a Core Data item. Everything about my app works, except when I press the button to read or write an NFC tag while testing on device, the NFC Read Card shows, but the tag won't be read. Occasionally, maybe 1 in 50 attempts, everything works fine and the tag will read, I get haptic feedback, and the card dismisses. I have confirmed with a 3rd party app that my device and the tags are compatible.
The project is here on Github. I will copy what I believe to be all relevant code here. I would create a minimum viable product, but I don't know what specifically is acting up.
This handles the NFC logic. It is copied from SwiftNFC. I wasn't able to get it to work when importing as a package.
import SwiftUI
import CoreNFC
@available(iOS 13.0, *)
public class NFCReader: NSObject, ObservableObject, NFCNDEFReaderSessionDelegate {
public var startAlert = "Hold your iPhone near the tag."
public var endAlert = ""
public var msg = "Scan to read or Edit here to write..."
public var raw = "Raw Data available after scan."
public var session: NFCNDEFReaderSession?
public func read() {
guard NFCNDEFReaderSession.readingAvailable else {
print("Error")
return
}
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = self.startAlert
session?.begin()
}
public func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
DispatchQueue.main.async {
self.msg = messages.map {
$0.records.map {
String(decoding: $0.payload, as: UTF8.self)
}.joined(separator: "\n")
}.joined(separator: " ")
self.raw = messages.map {
$0.records.map {
"\($0.typeNameFormat) \(String(decoding:$0.type, as: UTF8.self)) \(String(decoding:$0.identifier, as: UTF8.self)) \(String(decoding: $0.payload, as: UTF8.self))"
}.joined(separator: "\n")
}.joined(separator: " ")
session.alertMessage = self.endAlert != "" ? self.endAlert : "Club Scanned!"
}
}
public func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
}
public func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
print("Session did invalidate with error: \(error)")
self.session = nil
}
}
public class NFCWriter: NSObject, ObservableObject, NFCNDEFReaderSessionDelegate {
public var startAlert = "Hold your iPhone near the tag."
public var endAlert = ""
public var msg = ""
public var type = "T"
public var session: NFCNDEFReaderSession?
public func write() {
guard NFCNDEFReaderSession.readingAvailable else {
print("Error")
return
}
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = self.startAlert
session?.begin()
}
public func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
}
public func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
if tags.count > 1 {
let retryInterval = DispatchTimeInterval.milliseconds(500)
session.alertMessage = "Detected more than 1 tag. Please try again."
DispatchQueue.global().asyncAfter(deadline: .now() + retryInterval, execute: {
session.restartPolling()
})
return
}
let tag = tags.first!
session.connect(to: tag, completionHandler: { (error: Error?) in
if nil != error {
session.alertMessage = "Unable to connect to tag."
session.invalidate()
return
}
tag.queryNDEFStatus(completionHandler: { (ndefStatus: NFCNDEFStatus, capacity: Int, error: Error?) in
guard error == nil else {
session.alertMessage = "Unable to query the status of tag."
session.invalidate()
return
}
switch ndefStatus {
case .notSupported:
session.alertMessage = "Tag is not NDEF compliant."
session.invalidate()
case .readOnly:
session.alertMessage = "Read only tag detected."
session.invalidate()
case .readWrite:
let payload: NFCNDEFPayload?
if self.type == "T" {
payload = NFCNDEFPayload.init(
format: .nfcWellKnown,
type: Data("\(self.type)".utf8),
identifier: Data(),
payload: Data("\(self.msg)".utf8)
)
} else {
payload = NFCNDEFPayload.wellKnownTypeURIPayload(string: "\(self.msg)")
}
let message = NFCNDEFMessage(records: [payload].compactMap({ $0 }))
tag.writeNDEF(message, completionHandler: { (error: Error?) in
if nil != error {
session.alertMessage = "Write to tag fail: \(error!)"
} else {
session.alertMessage = self.endAlert != "" ? self.endAlert : "Write \(self.msg) to tag successful."
}
session.invalidate()
})
@unknown default:
session.alertMessage = "Unknown tag status."
session.invalidate()
}
})
})
}
public func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
}
public func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
print("Session did invalidate with error: \(error)")
self.session = nil
}
}
Here is the View. I'll remove what I can here for readability, but it wont wont run now. Pull the file from github to run in Xcode.
import SwiftUI
//import SwiftNFC
import CoreNFC
struct ClubListView: View {
@EnvironmentObject var storefront: Storefront
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var locationManager: LocationManagerModel
@FetchRequest(
entity: Club.entity(),
sortDescriptors:[])
private var clubs:FetchedResults<Club>
@ObservedObject var NFCR = NFCReader()
@ObservedObject var NFCW = NFCWriter()
@State private var shotClub = Club()
@State private var ballClub = Club()
@State private var showNewClub: Bool = false
@State private var showSettings: Bool = false
@State var premium = false
@Binding var refreshGames: UUID
@Binding var refreshClubs: UUID
@Binding var counter: Int
@Binding var puttCounter: Int
@Binding var roundStarted: Bool
//@Binding var waiting: Bool
//@Binding var showError: Bool
var body: some View {
NavigationView{
ZStack {
HStack{
Spacer()
VStack{
Spacer()
if premium {
Button(action: {
read()
////READ NFC TAG
}, label: {
ReadNFCButton()
.frame(width: 50, height: 50, alignment: .center)
})
.padding(.bottom, 30)
.padding(.horizontal, 30)
.shadow(color: Color.black.opacity(0.3), radius: 3, x: -3.0, y: 0.0)
}
}
}
.zIndex(1.0)
} //: ZStack
}.onChange(of: NFCR.msg) {newValue in
print("test")
if !locationManager.waiting && !shotClub.putter{
locationManager.currentLocation(mode: .shot)
shotClub.name = NFCR.msg
} else if locationManager.waiting && shotClub.putter {
locationManager.currentLocation(mode: .ball)
ballClub = shotClub
if roundStarted {
puttCounter += 1
counter += 1
}
shotClub.strokesList.append(0)
if ballClub.putter == true {
locationManager.waiting = false
}
} else if !locationManager.waiting && shotClub.putter {
if roundStarted{
counter += 1
puttCounter += 1
}
shotClub.strokesList.append(0)
}
}
}
func read() {
NFCR.read()
}
}
struct ReadNFCButton: View {
var body: some View {
VStack{
Spacer()
HStack{
Spacer()
Image(systemName: "wave.3.left.circle")
.symbolRenderingMode(.monochrome)
.resizable()
.foregroundColor(.green)
.aspectRatio(1.0, contentMode: .fit)
}
}
}
}