I've just updated to iOS 16, and all of a sudden the same code I used in iOS 15 is now reading ndefMessage as nil in the didDetect callback. I can't find anything online regarding what in iOS 16 would cause this, has anyone seen anything similar?
When I scan the same tag on Android, or use the NFC Tools app on iOS, I can read the tag NDEF fine. It seems that just my code seems to have been affected by the update...
UPDATE 1: I have put the same code onto my iOS 15.6 device, and it works perfectly. It seems to me that this is an iOS 16 bug.
Here's what I have:
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
print("did detect")
let str: String = nfcWriteContent
if (tags.count > 1) {
let retryInterval = DispatchTimeInterval.milliseconds(500)
session.alertMessage = "too_many_nfc_detected".localized()
DispatchQueue.global().asyncAfter(deadline: .now() + retryInterval, execute: {
session.restartPolling()
})
return
}
let tag = tags.first!
print("reading...")
tag.readNDEF(completionHandler: {(ndefMessage: NFCNDEFMessage?, error: Error?) in
var res = ""
if (ndefMessage == nil) {
// EVERY NFC SCAN ALWAYS FALLS IN HERE NOW
// WHEN SCANNING THE SAME TAG ON ANDROID, NDEF CONTENT IS PROPERLY RETURNED
print("empty tag")
} else {
print (ndefMessage!.records.count)
for payload in ndefMessage!.records {
if (payload.payload.count == 0) {
continue
}
res += (String.init(data: payload.payload.advanced(by: 1), encoding: .utf8) ?? "Format not supported")
}
}
session.alertMessage = "tag_successfully_read".localized()
session.invalidate()
print("read \(res)")
})
}
I got feedback from an Apple engineer. The sample project runs fine on iOS 16. You are missing out that you are not connected to the tag:
try await ndefReaderSession?.connect(to: tag)
let message = try await tag.readNDEF()
or
ndefReaderSession?.connect(to: tag) { error in
//call tag.readNDEF here
}
Related
I have the following SwiftUI code where a simple button brings up the iOS file manager and allows the user to select a CSV file to be imported. I've found that it works well for files that are stored locally on my device but if I try to select a file from Google Drive or OneDrive it gets a URL but when I then try to retrieve the data from it, it returns an error saying that the file was not found.
After a lot of head scratching, I've found that when using the file browser if I long press to bring up the context menu and then view the info for the file (which I'm guessing may be pulling it down to the phones local cache), it will then work as expected. This is shown in the following animated gif:
I've found that once I've done that caching trick, I can access the file without issue in other apps using the same code and I've also found that I can uninstall my app and reinstall it and it continues to work.
Can anyone advise on an approach using SwiftUI where I can avoid this File Not Found error when trying to import the file from Google Drive or OneDrive?
The entire code that I've been using for testing is as follows:
import SwiftUI
struct ContentView: View {
#State private var isImporting: Bool = false
#State private var fileContentString = ""
#State var alertMsg = ""
#State var showAlert = false
func reportError(error: String) {
alertMsg = error
showAlert.toggle()
}
var body: some View {
VStack {
Button(action: { isImporting = true}, label: {
Text("Select CSV File")
})
.padding()
Text(fileContentString) //This will display the imported CSV as text in the view.
}
.padding()
.fileImporter(
isPresented: $isImporting,
allowedContentTypes: [.commaSeparatedText],
allowsMultipleSelection: false
) { result in
do {
guard let selectedFileURL: URL = try result.get().first else {
alertMsg = "ERROR: Result.get() failed"
self.reportError(error: alertMsg)
return
}
print("selectedFileURL is \(selectedFileURL)")
if selectedFileURL.startAccessingSecurityScopedResource() {
//print("startAccessingSecurityScopedResource passed")
do {
print("Getting Data from URL...")
let inputData = try Data(contentsOf: selectedFileURL)
print("Converting data to string...")
let inputString = String(decoding: inputData, as: UTF8.self)
print(inputString)
fileContentString = inputString
}
catch {
alertMsg = "ERROR: \(error.localizedDescription)"
self.reportError(error: alertMsg)
print(alertMsg)
}
//defer { selectedFileURL.stopAccessingSecurityScopedResource() }
} else {
// Handle denied access
alertMsg = "ERROR: Unable to read file contents - Access Denied"
self.reportError(error: alertMsg)
print(alertMsg)
}
} catch {
// Handle failure.
alertMsg = "ERROR: Unable to read file contents - \(error.localizedDescription)"
self.reportError(error: alertMsg)
print(alertMsg)
}
}
.alert(isPresented: $showAlert, content: {
Alert(title: Text("Message"), message: Text(alertMsg), dismissButton: .destructive(Text("OK"), action: {
}))
})
}
}
The console log output is as follows:
selectedFileURL is file:///private/var/mobile/Containers/Shared/AppGroup/8F147702-8630-423B-9DA0-AE49667748EB/File%20Provider%20Storage/84645546/1aTSCPGxY3HzILlCIFlMRtx4eEWDZ2JAq/example4.csv
Getting Data from URL...
ERROR: The file “example4.csv” couldn’t be opened because there is no such file.
selectedFileURL is file:///private/var/mobile/Containers/Shared/AppGroup/8F147702-8630-423B-9DA0-AE49667748EB/File%20Provider%20Storage/84645546/1aTSCPGxY3HzILlCIFlMRtx4eEWDZ2JAq/example4.csv
Getting Data from URL...
Converting data to string...
First Name,Last Name
Luke,Skywalker
Darth,Vader
My testing has been done on a physical iPhone 12 Pro Max running iOS 14.2 and a physical iPad Air 2 running iPadOS 14.4.
I found an answer to my issue. The solution was to use a NSFileCoordinator() to force the file to be downloaded.
With the code below, if I access a file in cloud storage that hasn't been previously downloaded to the local device it will print "FILE NOT AVAILABLE" but it will now just download the file rather than throwing a file not found error.
Ideally I would like to be able to download just the file property metadata first to check how big the file is and then decide if I want to download the full file. The NSFileCoordinator has a metadata only option but I haven't worked out how to retrieve and interpret the results from that. This will do for now...
if selectedFileURL.startAccessingSecurityScopedResource() {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: selectedFileURL.path) {
print("FILE AVAILABLE")
} else {
print("FILE NOT AVAILABLE")
}
var error: NSError?
NSFileCoordinator().coordinate(
readingItemAt: selectedFileURL, options: .forUploading, error: &error) { url in
print("coordinated URL", url)
let coordinatedURL = url
isShowingFileDetails = false
importedFileURL = selectedFileURL
do {
let resources = try selectedFileURL.resourceValues(forKeys:[.fileSizeKey])
let fileSize = resources.fileSize!
print ("File Size is \(fileSize)")
} catch {
print("Error: \(error)")
}
}
do {
print("Getting Data from URL...")
let inputData = try Data(contentsOf: selectedFileURL)
print("Do stuff with file....")
}
}
I'm currently facing a problem with implementing a specific feature into an NFC based iOS 13 app. When reading a tag, I'd like to get the unique hardware id and the NDEF message in one session. So far I've checked different sample projects, including code provided by Apple and was able to get the information I'm interested in, but not in the same reading session.
I simplified the following code snippets to better focus on the problem (missing error checks, etc.).
Using an NFCTagReaderSession to get the unique hardware id:
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
let tag = tags.first!
session.connect(to: tag) { (error: Error?) in
if case let .miFare(mifareTag) = tag {
print(mifareTag.identifier as NSData) //this is the info I'm interested in
}
}
}
The payload type of a message record however seems to be only available in a NFCNDEFReaderSession:
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
let tag = tags.first!
session.connect(to: tag, completionHandler: { (error: Error?) in
tag.queryNDEFStatus(completionHandler: { (ndefStatus: NFCNDEFStatus, capacity: Int, error: Error?) in
tag.readNDEF(completionHandler: { (message: NFCNDEFMessage?, error: Error?) in
var statusMessage: String
if nil != error || nil == message {
statusMessage = "Fail to read NDEF from tag"
} else {
statusMessage = "Found 1 NDEF message"
let payload = message.records.first!
if let type = String(data: payload.type, encoding: .utf8) {
print("type:%#", type) //this is the info I'm interested in
}
}
session.alertMessage = statusMessage
session.invalidate()
})
})
})
}
As you can see, I can either get the hardware id, using a NFCTagReaderSession or the payload type of a message record, using a NFCNDEFReaderSession. Am I missing something here or are there indeed two different reading sessions required to get the information I'm interested in? Thanks in advance.
I finally found the solution for this :)
You can actually read the NDEF data in the delegate functions of your NFCTagReaderSession but in iOS 13 you have to use another delegate.
It seems in iOS 11 and 12 NFCTag was a protocol but in iOS 13 NFCTag became an enum and the former protocol was renamed to __NFCTag.
An __NFCTag can be casted to an NFCNDEFTag and then you can read NDEF data as usual.
To get an __NFCTag in your delegate functions you need to use __NFCTagReaderSessionDelegate instead.
To initialize your session you prepend __ to the pollingOption argument label of the initializer.
To connect to the tag you need to use __connect.
Here is how to read the identifier and the NDEF data.
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [__NFCTag]) {
let tag = tags.first!
session.__connect(to: tag) { (error: Error?) in
if let ndefTag = tag as? NFCNDEFTag {
if let miFareTag = ndefTag as? NFCMiFareTag {
// here you can get miFareTag.identifier like in your first code sample
}
ndefTag.readNDEF(completionHandler: { (message, error) in
// here you can read NDEF data like in your second code sample
})
}
}
}
I had the same issue and found solution in Apple's sample project, which I hardly recommend to read.
FYI:
Avoid using classes and methods with underscores as a prefix, it could lead you to the application rejection on AppStore.
So i am trying to monitor the connection status by closers :
func reconnect(success: #escaping () -> Void, failure: #escaping () -> Void) {
let manager = NEHotspotConfigurationManager.shared
let ssid = CameraManager.camera.uuid
let password = "password"
let isWEP = false
let hotspotConfiguration = NEHotspotConfiguration(ssid: ssid, passphrase: password, isWEP: isWEP)
hotspotConfiguration.joinOnce = true
manager.apply(hotspotConfiguration) { (error) in
if (error != nil) {
if let error = error {
switch error._code {
case 8:
print("internal error")
failure()
case 7:
NotificationCenter.default.post(name: Notification.Name(rawValue: "cancelFromHotSpot"), object: nil)
failure()
self.stopSession()
case 13:
success()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.startSession()
}
default:
break
}
}
if error == nil {
print("success connecting wifi")
success()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.startSession()
}
}
}
}
Yet there is a scenario that i am getting this alert "Unable to join the network" while error is nil, any ideas?
I think this behavior is a iOS bug and we cannot avoid.
This problem was also discussed in Apple Developer Forum and answer of Apple staff was below
"I’ve got nothing to say here beyond what I said on 13 Feb. The fact that errors from the Wi-Fi subsystem don’t get reported via the completion handler is expected behaviour. If you don’t like that behavior — and, to be clear, I personally agree with you about that — the best way forward is to file a bug report requesting that it be changed. Please post your bug number, just for the record."
This was discussed here
So I do not have great ideas, unfortunately. All ideas I have are two below (these do not solve this problem perfectly.)
Wait for a bug fix in the future release.
Separate "Applying Configuration" code & Communication code like below.
#IBAction func setConfigurationButtonTapped(_ sender : Any) {
manager.apply(hotspotConfiguration) { (error) in
if(error != nil){
// Do error handling
}else{
// Wait a few seconds for the case of showing "Unable to join the..." dialog.
// Check reachability to the device because "error == nil" does not means success.
}
}
#IBAction func sendButtonTapped(_ sender : Any) {
self.startSession()
}
iOS 13 - Patch
I had the same problem, but I solved it by deleting first all the existing configuration entries:
NEHotspotConfigurationManager.shared.getConfiguredSSIDs { (wifiList) in
wifiList.forEach { NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: $0) }
// ... from here you can use your usual approach to autoconnect to your network
}
Maybe it's not always a possible solution since it's a bit drastic, but for me worked like a charm.
PS: I use this in an app that runs iOS 13. As far as I know should work also on iOS 11 and 12, but I didn't test it.
Remove hotspotConfiguration.joinOnce = true work for me
I have created an AB Test on Firebase(not Published yet) and added token of a test device.
But the value is not coming for the provided key.
As AB testing is in Beta, so is it a bug on Firebase side that it doesn't work on test devices?
let remoteConfig = RemoteConfig.remoteConfig()
remoteConfig.fetch(withExpirationDuration: 0) { status, _ in
if status == RemoteConfigFetchStatus.success {
remoteConfig.activateFetched()
let ab_test_value = remoteConfig.configValue(forKey: "ab_test_key").stringValue
print(ab_test_value)
}}
The ab_test_value is coming as empty.
Try this may your variable is deallocated when response come
var remoteConfig:FIRRemoteConfig?
///
var expirationDuration = 43200;
// If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
// the server.
if (remoteConfig?.configSettings.isDeveloperModeEnabled)!
{
expirationDuration = 0;
}
self.remoteConfig?.fetch(withExpirationDuration: TimeInterval(expirationDuration))
{
(status, error) -> Void in
if status == .success
{
print("Config fetched!")
self.remoteConfig?.activateFetched()
}
else
{
print("Config not fetched")
print("Error \(error!.localizedDescription)")
// return
}
self.displayWelcome()
}
}
Loading data from firestore while offline works as expected but a call to save never returns and there seems to be no timeout either.
This is a example save that works online but not offline:
func save() {
guard let uid = user?.uid else {
return
}
let db = Firestore.firestore()
var ref: DocumentReference? = nil
ref = db.collection("users").document(uid).collection("properties").addDocument(data: ["name": "test"]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
}
Is there any known workaround?
UPDATE: Firebase support have confirmed it's a bug and that it "is now being worked on by our engineers". They are unable to give a timescale for when it will be fixed.
This is the expected behaviour - you should assume that the write will happen when the device comes back online. In my use cases, I've just continued with the normal flow and used my local data as my source of truth.
It's mentioned here: https://youtu.be/XrltP8bOHT0?t=680