I was following issue with using Firebase/Firestore SDK:
Precondition failed: NSArray element failed to match the Swift Array Element type
Expected FIRQueryDocumentSnapshot but found FIRQueryDocumentSnapshot: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1001.2.63.12/swift/stdlib/public/core/ArrayBuffer.swift, line 346
2019-05-18 19:46:00.020040+0200 App[25051:288337] Precondition failed: NSArray element failed to match the Swift Array Element type
Expected FIRQueryDocumentSnapshot but found FIRQueryDocumentSnapshot: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1001.2.63.12/swift/stdlib/public/core/ArrayBuffer.swift, line 346
let listener = self.client
.collection("countries/czechia/cities/\(id.rawValue)/venues")
.addSnapshotListener({ (snapshot, error) in
guard let docs = snapshot?.documents else {
observer.onError(error!)
return
}
let arr: [Venue] = docs.compactMap { doc in // The code crashes on this line
do {
let decoded = try self.decoder.decode(Venue.self, from: doc.data())
return decoded
} catch {
print(error)
return nil
}
}
}
This error it's very common when you work with umbrella framework technique.
I was getting "Precondition failed: NSArray element failed to match the Swift Array Element type
Expected FIRQueryDocumentSnapshot but found FIRQueryDocumentSnapshot bla bla bla bla..." crash error in spite of having just one firebase instance reference to static framework working.
So I had to use the powerful NSObject features in particular Key-Value Coding protocol.
Check this....
let db = Firestore.firestore(app: firebase_instance)
defaultsHelper.write(value: true, key: .isReceivingProspects)
prospectosListener = db.collection("collection_name")
.document("document_name")
.collection("collection_name")
.whereField("condition_parameter", arrayContains: "condition_value")
.addSnapshotListener { querySnapshot, error in
weak var _self = self
guard let snapshot = querySnapshot else {
print("Error fetching document: \(error!)")
return
}
// snapshot.documents or snapshot.documentChanges in a loop produces crash
guard let documents = (snapshot as NSObject).value(forKey: "documentChanges") as? NSArray else { return }
for document in documents {
guard let object = document as? NSObject else { debugPrint("object was nil"); return }
guard let type = object.value(forKey: "type") as? Int else { debugPrint("type was nil"); return }
guard let docs = object.value(forKey: "document") as? NSObject else { debugPrint("document was nil"); return }
guard let data = docs.value(forKey: "data") as? [String: Any] else { debugPrint("data was nil"); return }
guard let fbModel = _self?.documentConverter.convertToNotificationModel(documentData: data) else {
debugPrint("fbModel was nil")
return
}
switch type {
case 0: // Added
_self?.onAddedOrModifiedNotificationEvent(fbModel: fbModel)
case 1: // Modified
_self?.onAddedOrModifiedNotificationEvent(fbModel: fbModel)
case 2: // Removed
_self?.onDeleteNotificationEvent(fbModel: fbModel)
default:
debugPrint("Another option")
}
}
Related
This is my code
let db = Firestore.firestore()
db.collection("chats").document(userDefaults.string(forKey: "currentGroup")!).collection("messages").document("variable").addSnapshotListener { (snapshot, error) in
if error != nil{
print("Error fetching document")
}
else{
let documentData = snapshot!.data()
print(documentData!["numOfMessages"])
self.numOfMessages = documentData!["numOfMessages"] as! Int
print(self.numOfMessages)
//Get texts and display them
db.collection("chats").document(self.userDefaults.string(forKey: "currentGroup")!).collection("messages").document("\(self.numOfMessages)").getDocument { (document, err) in
let newMessageData = document!.data()
let newMessage = newMessageData!["message"] as! String
let newAuthor = newMessageData!["author"] as! String
let authorLabel = UILabel()
authorLabel.text = newAuthor
self.stackView.addArrangedSubview(authorLabel)
let label = UILabel()
label.text = newMessage
self.stackView.addArrangedSubview(label)
}
}
}
This line self.numOfMessages = documentData!["numOfMessages"] as! Int has an error of
Could not cast value of type 'NSTaggedPointerString' (0x1ed6ed450) to 'NSNumber' (0x1ed6f98c8).
This is every since I deleted the collection messages and replaced it with one of the exact same name
The value that documentData!["numOfMessages"] returns is Optional(1) even though in firebase the value is 2.
This is how the Firestore looks:
Either you are listening to the wrong document (perhaps because of an incorrect user default) or you are unwrapping the value incorrectly. To debug this, try the following and see what the problem actually is. The following is a more idiomatic way of handling documents.
if let currentGroup = userDefaults.string(forKey: "currentGroup") {
print(currentGroup) // this could be your problem
Firestore.firestore().collection("chats").document(currentGroup).collection("messages").document("variable").addSnapshotListener { (snapshot, error) in
if let snapshot = snapshot {
if let numOfMessages = snapshot.get("numOfMessages") as? Int {
print(numOfMessages)
} else {
print("field error")
}
} else if let error = error {
print(error)
}
}
} else {
print("no current group")
}
I have been working on Firestore for retrieving data, when I tried to get data from collection->document id-> field. refer the below screen shot, I need to check companyCode matches with user entered companyCode.text
I tried with below code, need to check whether the user entered companyCodeLabel.text matches document "companyCode" and also get documentId. Can anyone suggest how to solve this?
guard let code = companyCodeLabel.text else { return }
let docRef = db.collection("Company").whereField("companyCode", isEqualTo: code).limit(to: 1)
docRef.getDocuments { (querysnapshot, error) in
if error != nil {
print("Document Error: ", error!)
} else {
if let doc = querysnapshot?.documents, !doc.isEmpty {
print("Document is present.")
}
}
}
Even tried to print the field value in collection but still have crash and same error nil
self.db.collection("Company").getDocuments { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in snapshot!.documents {
let docId = document.documentID
let compCode = document.get("companyCode") as! String
let compName = document.get("companyName") as! String
print(docId, compCode, compName)
}
}
}
I tried to call in wrong db, I was trying var db = Firestore!,
The correct solutions is
Firestore.firestore().collection("Company").getDocuments { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in snapshot!.documents {
let docId = document.documentID
let compCode = document.get("companyCode") as! String
let compName = document.get("companyName") as! String
print(docId, compCode, compName)
}
}
I am not able to append the documents retrieved from the Firestore database in chat application based on Swift IOS to the "messages" variable, after appending I have configure the table cells as below in the code, I am getting the following error
Error
Cannot convert value of type '[QueryDocumentSnapshot]' to expected argument type 'DocumentSnapshot'
Code
var messages: [DocumentSnapshot]! = []
func configuredatabase ()
{
db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
//here is the error
self.messages.append(documents)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue cell
let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
// Unpack message from Firebase DataSnapshot
let messageSnapshot = self.messages![indexPath.row]
guard let message = messageSnapshot as? [String:String] else { return cell }
let name = message[Constants.MessageFields.name] ?? ""
if let imageURL = message[Constants.MessageFields.imageURL] {
if imageURL.hasPrefix("gs://") {
Storage.storage().reference(forURL: imageURL).getData(maxSize: INT64_MAX) {(data, error) in
if let error = error {
print("Error downloading: \(error)")
return
}
DispatchQueue.main.async {
cell.imageView?.image = UIImage.init(data: data!)
cell.setNeedsLayout()
}
}
} else if let URL = URL(string: imageURL), let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage.init(data: data)
}
cell.textLabel?.text = "sent by: \(name)"
} else {
let text = message[Constants.MessageFields.text] ?? ""
cell.textLabel?.text = name + ": " + text
cell.imageView?.image = UIImage(named: "ic_account_circle")
if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage(data: data)
}
}
return cell
}
While there are two other very good answers, there may be some confusion between a
FIRDocumentSnapshot (Note: renamed to DocumentSnapshot)
Which is returned when you want to get a specific document: someDoc.getDocument(
and
FIRQuerySnapshot (Note: renamed to QuerySnapshot)
Which is returned when an observer is added to a collection or a series of documents is being retrieved: someCollection.getDocuments and then each document within QuerySnapshot is a discreet FIRQueryDocumentSnapshot (renamed to QueryDocumentSnapshot). (e.g. iterate over the QuerySnapshot to get the child QueryDocumentSnapshot)
Note that DocumentSnapshot may return nil in data property if the document doesn't exists, so it can be tested for .exists. Whereas QueryDocumentSnapshot will never be nil (exists is always true) because deleted data is not returned.
In the question, an observer is being added to a collection with
.collection("messages").addSnapshotListener
therefore the data returned is a QuerySnapshot and to store it as a var, the var type would need to match
var messagesQuerySnapshot: QuerySnapshot!
and then inside the listener
db.collection("messages")...addSnapshotListener { querySnapshot, error in
messagesQuerySnapshot = querySnapshot
However, I would not recommend that.
I would suggest a messages class that can be initialize with the data retrieved from Firestore and store those in an array.
class MessagesClass {
var msg_id = ""
var msg = ""
var from = ""
convenience init(withQueryDocSnapshot: QueryDocumentSnapshot) {
//init vars from the document snapshot
}
}
and then a class var datasource array to hold them
var messagesArray = [MessageClass]()
and then code to read the messages, create the message objects and add them to the dataSource array
db.collection("messages")...getDocuments { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
for doc in snapshot.documents {
let aMsg = MessageClass(withQueryDocSnapshot: doc)
self.messagesArray.append(aMsg)
}
}
NOTE: we are not adding an listener here, we are getting the documents one time. If you want to add a listener to watch for users being added, changed or removed, additional code is needed to detect the changes.
See the Firebase Documentation on Viewing Changes Between Snapshots
Replace self.messages.append(documents) with self.messages.append(contentsOf: documents)
The first method takes a single element and the second one takes a collection which is in your case.
https://developer.apple.com/documentation/swift/array/3126937-append
https://developer.apple.com/documentation/swift/array/3126939-append
var messages: [[String: Any]] = []
db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
for doc in documents {
self.messages.append(doc.data())
}
}
Since upgrading to Swift 4.2 I've found that many of the NSKeyedUnarchiver and NSKeyedArchiver methods have been deprecated and we must now use the type method static func unarchivedObject<DecodedObjectType>(ofClass: DecodedObjectType.Type, from: Data) -> DecodedObjectType? to unarchive data.
I have managed to successfully archive an Array of my bespoke class WidgetData, which is an NSObject subclass:
private static func archiveWidgetDataArray(widgetDataArray : [WidgetData]) -> NSData {
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: widgetDataArray as Array, requiringSecureCoding: false) as NSData
else { fatalError("Can't encode data") }
return data
}
The problem comes when I try to unarchive this data:
static func loadWidgetDataArray() -> [WidgetData]? {
if isKeyPresentInUserDefaults(key: USER_DEFAULTS_KEY_WIDGET_DATA) {
if let unarchivedObject = UserDefaults.standard.object(forKey: USER_DEFAULTS_KEY_WIDGET_DATA) as? Data {
//THIS FUNCTION HAS NOW BEEN DEPRECATED:
//return NSKeyedUnarchiver.unarchiveObject(with: unarchivedObject as Data) as? [WidgetData]
guard let nsArray = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSArray.self, from: unarchivedObject as Data) else {
fatalError("loadWidgetDataArray - Can't encode data")
}
guard let array = nsArray as? Array<WidgetData> else {
fatalError("loadWidgetDataArray - Can't get Array")
}
return array
}
}
return nil
}
But this fails, as using Array.self instead of NSArray.self is disallowed. What am I doing wrong and how can I fix this to unarchive my Array?
You can use unarchiveTopLevelObjectWithData(_:) to unarchive the data archived by archivedData(withRootObject:requiringSecureCoding:). (I believe this is not deprecated yet.)
But before showing some code, you should better:
Avoid using NSData, use Data instead
Avoid using try? which disposes error info useful for debugging
Remove all unneeded casts
Try this:
private static func archiveWidgetDataArray(widgetDataArray : [WidgetData]) -> Data {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: widgetDataArray, requiringSecureCoding: false)
return data
} catch {
fatalError("Can't encode data: \(error)")
}
}
static func loadWidgetDataArray() -> [WidgetData]? {
guard
isKeyPresentInUserDefaults(key: USER_DEFAULTS_KEY_WIDGET_DATA), //<- Do you really need this line?
let unarchivedObject = UserDefaults.standard.data(forKey: USER_DEFAULTS_KEY_WIDGET_DATA)
else {
return nil
}
do {
guard let array = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(unarchivedObject) as? [WidgetData] else {
fatalError("loadWidgetDataArray - Can't get Array")
}
return array
} catch {
fatalError("loadWidgetDataArray - Can't encode data: \(error)")
}
}
But if you are making a new app, you should better consider using Codable.
unarchiveTopLevelObjectWithData(_:)
is deprecated as well. So to unarchive data without secure coding you need to:
Create NSKeyedUnarchiver with init(forReadingFrom: Data)
Set requiresSecureCoding of created unarchiver to false.
Call decodeObject(of: [AnyClass]?, forKey: String) -> Any? to get your object, just use proper class and NSKeyedArchiveRootObjectKeyas key.
As unarchiveTopLevelObjectWithData is also deprecated after iOS 14.3 only the Hopreeeenjust's answer is correct now.
But if you don't need NSSecureCoding you also can use answer of Maciej S
It is very easy to use it, by adding extension to NSCoding protocol:
extension NSCoding where Self: NSObject {
static func unsecureUnarchived(from data: Data) -> Self? {
do {
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = false
let obj = unarchiver.decodeObject(of: self, forKey: NSKeyedArchiveRootObjectKey)
if let error = unarchiver.error {
print("Error:\(error)")
}
return obj
} catch {
print("Error:\(error)")
}
return nil
}
}
With this extension to unarchive e.g. NSArray you only need:
let myArray = NSArray.unsecureUnarchived(from: data)
For Objective C use NSObject category:
+ (instancetype)unsecureUnarchivedFromData:(NSData *)data {
NSError * err = nil;
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData: data error: &err];
unarchiver.requiresSecureCoding = NO;
id res = [unarchiver decodeObjectOfClass:self forKey:NSKeyedArchiveRootObjectKey];
err = err ?: unarchiver.error;
if (err != nil) {
NSLog(#"NSKeyedUnarchiver unarchivedObject error: %#", err);
}
return res;
}
Note that if the requiresSecureCoding is false, class of unarchived object is not actually checked and objective c code returns valid result even if it is called from wrong class.
And swift code when called from wrong class returns nil (because of optional casting), but without error.
Swift 5- IOS 13
guard let mainData = UserDefaults.standard.object(forKey: "eventDetail") as? NSData
else {
print(" data not found in UserDefaults")
return
}
do {
guard let finalArray =
try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(mainData as Data) as? [EventDetail]
else {
return
}
self.eventDetail = finalArray
}
You are likely looking for this:
if let widgetsData = UserDefaults.standard.data(forKey: USER_DEFAULTS_KEY_WIDGET_DATA) {
if let widgets = (try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, WidgetData.self], from: widgetsData)) as? [WidgetData] {
// your code
}
}
if #available(iOS 12.0, *) {
guard let unarchivedFavorites = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(favoritesData!)
else {
return
}
self.channelFavorites = unarchivedFavorites as! [ChannelFavorite]
} else {
if let unarchivedFavorites = NSKeyedUnarchiver.unarchiveObject(with: favoritesData!) as? [ChannelFavorite] {
self.channelFavorites = unarchivedFavorites
}
// Achieving data
if #available(iOS 12.0, *) {
// use iOS 12-only feature
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: channelFavorites, requiringSecureCoding: false)
UserDefaults.standard.set(data, forKey: "channelFavorites")
} catch {
return
}
} else {
// handle older versions
let data = NSKeyedArchiver.archivedData(withRootObject: channelFavorites)
UserDefaults.standard.set(data, forKey: "channelFavorites")
}
This is the way I have updated my code and its working for me
Just updated to Xcode 7.0 from Xcode 6.4. In my project I am getting an error now which I try to solve the whole night and did not get it.
The error message is: Initializer for conditional binding must have optional type not 'nsmanagedobjectcontext'
The error is coming twice in lines if let managedObjectContext = self.managedObjectContext { in following code
func preloadData () {
// Retrieve data from the source file
if let contentsOfURL = NSBundle.mainBundle().URLForResource("listofdata", withExtension: "csv") {
// Remove all the items before preloading
removeData()
var error:NSError?
if let items = parseCSV(contentsOfURL, encoding: NSUTF8StringEncoding, error: &error) {
// Preload the items
if let managedObjectContext = self.managedObjectContext {
for item in items {
let listOfItem = NSEntityDescription.insertNewObjectForEntityForName("ListOfItem", inManagedObjectContext: managedObjectContext) as! ListOfItem
listOfItem.name = item.name
listOfItem.address = item.address
listOfItem.phone = item.phone
if managedObjectContext.save(&error) != true {
print("insert error: \(error!.localizedDescription)")
}
}
}
}
}
}
func removeData () {
// Remove the existing items
if let managedObjectContext = self.managedObjectContext {
let fetchRequest = NSFetchRequest(entityName: "ListOfItem")
var e: NSError?
let listOfItems = managedObjectContext.executeFetchRequest(fetchRequest, error: &e) as! [ListOfItem]
if e != nil {
print("Failed to retrieve record: \(e!.localizedDescription)")
} else {
for listOfItem in listOfItems {
managedObjectContext.deleteObject(listOfItem)
}
}
}
}
Appreciate all help! Thanks!
Update:
The updated code looks like this, but still have these two errors in the first function preloadData:
Missing argument for parameter 'error' in call
Initializer for conditional binding must have Optional type, not 'NSManagedObjectContext'
func preloadData () {
// Retrieve data from the source file
if let contentsOfURL = NSBundle.mainBundle().URLForResource("listofdata", withExtension: "csv") {
// Remove all the menu items before preloading
removeData()
do {
let items = try parseCSV(contentsOfURL, encoding: NSUTF8StringEncoding)
// Preload the items
if let managedObjectContext = self.managedObjectContext {
for item in items {
let listOfItem = NSEntityDescription.insertNewObjectForEntityForName("ListOfItem", inManagedObjectContext: managedObjectContext) as! ListOfItem
listOfItem.name = item.name
listOfItem.address = item.address
listOfItem.phone = item.phone
if managedObjectContext.save() != true {
print("insert error: \(error.localizedDescription)")
}
}
}
} catch let error as NSError {
print("insert error: \(error.localizedDescription)")
}
}
This function shows no errors
func removeData () {
// Remove the existing items
let fetchRequest = NSFetchRequest(entityName: "ListOfItem")
do {
let listOfItems = try self.managedObjectContext.executeFetchRequest(fetchRequest) as! [ListOfItem]
for listOfItem in listOfItems {
self.managedObjectContext.deleteObject(listOfItem)
}
}
catch let error as NSError {
print("Failed to retrieve record: \(error.localizedDescription)")
}
Can some help? Thanks!
In Swift 2 the Core Data template implements the property managedObjectContext in AppDelegate as non-optional. Probably the updater changed the implementation accordingly.
The benefit is that the optional bindings are not necessary any more, but you have to consider the new error handling for example
func removeData () {
let fetchRequest = NSFetchRequest(entityName: "ListOfItem")
do {
let listOfItems = try self.managedObjectContext.executeFetchRequest(fetchRequest) as! [ListOfItem]
for listOfItem in listOfItems {
self.managedObjectContext.deleteObject(listOfItem)
}
}
catch let error as NSError {
print("Failed to retrieve record: \(error.localizedDescription)")
}
}
try below code
removeData()
func removeData () {
let fetchRequest = NSFetchRequest(entityName: "MenuItem")
do {
let listOfItems = try self.managedObjectContext.executeFetchRequest(fetchRequest) as! [MenuItem]
for listOfItem in listOfItems {
self.managedObjectContext.deleteObject(listOfItem)
}
}
catch let error as NSError {
print("Failed to retrieve record: \(error.localizedDescription)")
}
}
preloadData ()
func preloadData () {
// Retrieve data from the source file
if let contentsOfURL = NSBundle.mainBundle().URLForResource("menudata", withExtension: "csv") {
// Remove all the menu items before preloading
removeData()
var error:NSError?
if let items = parseCSV(contentsOfURL, encoding: NSUTF8StringEncoding, error: &error) {
// Preload the menu items
for item in items {
let menuItem = NSEntityDescription.insertNewObjectForEntityForName("MenuItem", inManagedObjectContext:self.managedObjectContext) as! MenuItem
menuItem.name = item.name
menuItem.detail = item.detail
menuItem.price = (item.price as NSString).doubleValue
}
}
}
}