SwiftUI App shows NFC card on screen but doesnt read tag

159 views Asked by At

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)
            }
        }
        
    }
}

0

There are 0 answers