Alex Huang
May 31
Estimated reading time: 13 minute(s)
import CoreNFC
import SwiftUI
final class HomeViewModel: NSObject, ObservableObject {
var session: NFCNDEFReaderSession?
func beginScanning() {
guard NFCNDEFReaderSession.readingAvailable else {
// Handle devices that don't support NFC reading
return
}
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = "Hold your iPhone near the item to learn more about it."
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
// Handle detected NFC tags
if let tag = tags.first {
session.connect(to: tag) { error in
if let error = error {
// Handle connection error
return
}
tag.queryNDEFStatus { status, capacity, error in
// Handle NDEF status and read data
}
}
}
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
// Handle session invalidation
}
}
import CoreNFC
import Foundation
final class CurrentNDEFMessage: ObservableObject {
@Published var ndefMessage: NFCNDEFMessage?
@Published var nfcData: NFCData?
@Published var editUUID: UUID?
func setNDEFMessageFromDB(item: NFCData) {
guard let recordType = item.record_type,
let identifier = item.identifier,
let payload = item.payload,
let format = NFCTypeNameFormat(rawValue: UInt8(item.format)) else {
return
}
if format == .absoluteURI || String(data: recordType, encoding: .utf8) == "U" {
ndefMessage = NFCNDEFMessage(records: [NFCNDEFPayload.wellKnownTypeURIPayload(string: String(data: payload, encoding: .utf8)!)!])
} else if String(data: recordType, encoding: .utf8) == "T" {
ndefMessage = NFCNDEFMessage(records: [NFCNDEFPayload.wellKnownTypeTextPayload(string: String(data: payload, encoding: .utf8)!, locale: .current)!])
} else {
ndefMessage = NFCNDEFMessage(records: [NFCNDEFPayload(format: format, type: recordType, identifier: identifier, payload: payload)])
}
ndefMessage?.records[0].identifier = identifier
nfcData = item
}
func setNewNFCNDEFMessage() {
ndefMessage = NFCNDEFMessage(records: [NFCNDEFPayload(format: .nfcWellKnown, type: "T".data(using: .utf8)!, identifier: Data(), payload: Data())])
}
}
import CoreData
final class HomeViewModel: NSObject, ObservableObject {
@Published var items: [NFCData] = []
func fetchItems(context: NSManagedObjectContext) {
let request: NSFetchRequest<NFCData> = NFCData.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \NFCData.timestamp, ascending: false)]
do {
items = try context.fetch(request)
} catch {
// Handle fetch error
}
}
func saveItem(context: NSManagedObjectContext, data: NFCData) {
do {
try context.save()
} catch {
// Handle save error
}
}
}
NSPersistentContainer
and loading the persistent stores.import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "SimpleNFC")
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { description, error in
if let error = error as NSError? {
// Handle error
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
}
}
NFCData
to store NFC message details.NFCData
entity includes attributes such as id
, identifier
, record_type
, payload
, timestamp
, and more. Refer to the NFCData Entity for the detailed model.NFCNDEFReaderSessionDelegate
methods.extension HomeViewModel: NFCNDEFReaderSessionDelegate {
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
if let tag = tags.first {
session.connect(to: tag) { error in
if let error = error {
// Handle connection error
return
}
tag.queryNDEFStatus { status, capacity, error in
switch status {
case .notSupported:
// Handle unsupported tags
break
case .readWrite:
// Handle read-write tags
break
case .readOnly:
// Handle read-only tags
break
case .unknown:
// Handle unknown tags
break
@unknown default:
// Handle future cases
break
}
}
}
}
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
// Handle session invalidation
}
}
import CoreData
import SwiftUI
final class DetailedNFCViewModel: ObservableObject {
@Published var record: NFCNDEFPayload?
@Published var identifier: Data = Data()
@Published var payloadTypeNameFormat: NFCTypeNameFormat = .unknown
@Published var recordType: Data = Data()
@Published var payload: Data = Data()
@Published var alertItem: AlertItem?
@Published var savedSuccessfully = false
var isValid: Bool {
guard !identifier.isEmpty else {
alertItem = AlertContext.nfcIdentiferInvalidated
return false
}
guard !payload.isEmpty else {
alertItem = AlertContext.nfcPayloadInvalidated
return false
}
return true
}
func saveChanges(context: NSManagedObjectContext, currentNDEFMessage: CurrentNDEFMessage) {
guard isValid else { return }
var item: NFCData
if let editUUID = currentNDEFMessage.editUUID, let nfcData = currentNDEFMessage.nfcData {
item = nfcData
item.last_accessed = Date()
} else {
item = NFCData(context: context)
item.id = UUID()
item.timestamp = Date()
item.last_accessed = Date()
}
item.identifier = self.identifier
item.format = Int16(self.payloadTypeNameFormat.rawValue)
item.record_type = self.recordType
item.payload = self.payload
do {
try context.save()
currentNDEFMessage.editUUID = nil
currentNDEFMessage.nfcData = nil
self.savedSuccessfully = true
} catch {
alertItem = AlertContext.failedToSave
}
}
}
import SwiftUI
import CoreNFC
struct DetailedNFCView: View {
@Binding var isActive: Bool
@Environment(\.managedObjectContext) private var viewContext
@StateObject private var viewModel = DetailedNFCViewModel()
@EnvironmentObject var currentNDEFMessage: CurrentNDEFMessage
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("NFC Data")
.font(.title)
.padding(.bottom, 10)
if viewModel.record != nil {
Form {
Section(header: Text("Identification")) {
TextField("Payload Identifier", text: Binding(
get: { String(data: viewModel.identifier, encoding: .utf8) ?? "N/A" },
set: { newValue in viewModel.identifier = newValue.data(using: .utf8) ?? Data() }
))
}
Section(header: Text("Payload Type")) {
Picker("Format", selection: $viewModel.payloadTypeNameFormat) {
ForEach(typeNameFormats, id: \.self) { format in
Text(format.description)
}
}
.pickerStyle(MenuPickerStyle())
Picker("Type", selection: Binding(
get: { String(data: viewModel.recordType, encoding: .utf8) ?? "N/A" },
set: { newValue in viewModel.recordType = newValue.data(using: .utf8) ?? Data() }
)) {
ForEach(recordTypes) { type in
Text(type.description)
}
}
.pickerStyle(MenuPickerStyle())
}
Section(header: Text("Payload")) {
TextEditor(text: Binding(
get: { String(data: viewModel.payload, encoding: .utf8) ?? "N/A" },
set: { newValue in viewModel.payload = newValue.data(using: .utf8) ?? Data() }
))
.frame(height: 100)
}
Button {
viewModel.saveChanges(context: viewContext, currentNDEFMessage: currentNDEFMessage)
} label: {
Text("Save NFC Data")
}
}
.cornerRadius(10)
} else {
Text("No NFC record available.")
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(10)
.onAppear {
if let record = currentNDEFMessage.ndefMessage?.records.first {
viewModel.record = record
viewModel.identifier = record.identifier
viewModel.payloadTypeNameFormat = record.typeNameFormat
viewModel.recordType = record.type
viewModel.payload = record.payload
}
}
.alert(item: $viewModel.alertItem) { alertItem in
Alert(title: alertItem.title, message: alertItem.message, dismissButton: alertItem.dismissButton)
}
.onChange(of: viewModel.savedSuccessfully) { isSaved in
if isSaved { isActive = false }
}
}
}