Read Array from Firebase - Swift - arrays

I am writing a function that allows me to read the data inside the Database. I can read the String from Firebase but I cannot read the arrays. I tried like this but it doesn't work:
func fetchData(collection: String) {
DatabaseFirestore.collection(collection).addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.example = documents.map { (queryDocumentSnapshot) -> Example in
let data = queryDocumentSnapshot.data()
let ex1 = data["ex1"] as? String ?? "" // it is not an array in the database
let ex2 = data["ex2"] as? String ?? "" // is an array in the database
return Example(ex1: ex1, ex2: ex2)
}
}
}
How could I change this line so that it reads the array inside the database?
let ex2 = data["ex2"] as? String ?? ""

Casting as [String] instead of String might fix the issue

You also could check this answer - it could be helpful when retrieving arrays and dictionaries from Firebase

Related

How to retrieve data held in an array from Firestore using Swift

this seems like it should be quite a simple thing to do, but can't seem to find a solution. I have some data in Firestore held in an array that I need to get and place into and two dimensional array inside my swift app. I'm pretty noob so apologies in advance!
This is the data I'm trying to get from Firestore
This is the code I'm using to query my documents and then loop through the result, extracting the data
fireStoreDatabase.collection("Posts").whereField("postedTo", arrayContains: userId).order(by: "date", descending: true).addSnapshotListener { (snapshot, error) in
if error != nil {
print(error?.localizedDescription)
} else {
if snapshot?.isEmpty != true && snapshot != nil {
print("Posted data got")
//clear the arrays to stop duplicates when we do an upload and pull in the data again
self.postedShoutTextArray.removeAll(keepingCapacity: false)
self.postedByArray.removeAll(keepingCapacity: false)
self.postedDateArray.removeAll(keepingCapacity: false)
self.postedQuestionsArray.removeAll(keepingCapacity: false)
//loop through the firestore
for document in snapshot!.documents {
//add data to the arrays
if let shoutText = document.get("shoutText") as? String {
self.postedShoutTextArray.append(shoutText)
}
if let postedBy = document.get("postedBy") as? String {
self.postedByArray.append(postedBy)
}
if let date = document.get("date") as? String {
self.postedDateArray.append(date)
}
if let pollQuestions = document.get("pollQuestions") as? [String] {
self.postedQuestionsArray = pollQuestions
} else {
print("no array data")
}
self.receivedCollectionView.reloadData()
}
} else {
print("GET POSTED MESSAGES no data")
}
}
}
So I'd like the data to go into a two dimensional array (if that's possible) containing the data from the pollQuestions array for each document I loop through (does that make sense?).
Been searching all over, and seen references to map etc, but had no luck finding a solution.
Any help appreciated!
Rather than storing each property in a separate array, you may want to consider representing it with a struct. Something like:
struct Item {
var shoutText: String?
var postedBy: String?
var date: String?
var pollQuestions : [String]
}
Then, on your view controller, declare a property:
var items: [Item] = []
Then, in your snapshot listener, you can populate that array:
func getData() {
Firestore.firestore().collection("Posts").whereField("postedTo", arrayContains: userId).order(by: "date", descending: true).addSnapshotListener { (snapshot, error) in
if error != nil {
print(error?.localizedDescription)
} else {
if let snapshot = snapshot, !snapshot.isEmpty {
print("Posted data got")
self.items = snapshot.documents.map { document in
Item(shoutText: document.get("shout") as? String,
postedBy: document.get("postedBy") as? String,
date: document.get("date") as? String,
pollQuestions: document.get("pollQuestions") as? [String] ?? [])
}
self.receivedCollectionView.reloadData()
} else {
print("GET POSTED MESSAGES no data")
}
}
}
}
Later, you can access this data:
self.items[itemIndex].pollQuestions[pollQuestionIndex]

Getting empty Array from Firestore Function (swift)

Hi this function is producing an unexpected result for me. I'm new to programming so I appreciate any help understanding this.
The first print statement prints second to the last print statement. I think its because maybe the call to Firebase is still waiting for results even though the function completed the operation? How can I make it so that the function doesn't return an empty array?
func readFromDatabase() -> [[String]] {
let db = Firestore.firestore()
db.collection("Letters").getDocuments { (snapshot, error) in
if error != nil {
print(error?.localizedDescription ?? "error")
} else {
for document in (snapshot?.documents)! {
// let fetchedData = document.data() //gets all data from all documenters unorganized
let firstLetter = document.get("1") as! String
let secondLetter = document.get("2") as! String
let thirdLetter = document.get("3") as! String
let fourthLetter = document.get("4") as! String
let fifthLetter = document.get("5") as! String
let sixthLetter = document.get("6") as! String
let seventhLetter = document.get("7") as! String
let eighthLetter = document.get("8") as! String
let organizedData = [firstLetter, secondLetter, thirdLetter, fourthLetter, fifthLetter, sixthLetter, seventhLetter, eighthLetter]
self.databaseData = organizedData
self.make2darray.append(self.databaseData) //makes 2darray
} //closing fetch
} //closing else statement
print("printing", self.make2darray) // works ok
} //closing snapshot
print("printing outside snapshot", self.make2darray) //returns empty array
return self.make2darray //returns empty array
} //closing function
UPDATE: I've made the following changes to the code after reading and trying a few things. This should work as far as I understood what I read but I'm still getting an empty array.
func readFromDatabase(completion: #escaping ([[String]]) -> Void) {
let db = Firestore.firestore()
let dispatchGroup = DispatchGroup()
db.collection("Letters").getDocuments { (snapshot, error) in
if error != nil {
print(error?.localizedDescription ?? "error")
} else {
dispatchGroup.enter()
for document in (snapshot?.documents)! {
// let fetchedData = document.data() //gets all data from all documenters unorganized
let firstLetter = document.get("1") as! String
let secondLetter = document.get("2") as! String
let thirdLetter = document.get("3") as! String
let fourthLetter = document.get("4") as! String
let fifthLetter = document.get("5") as! String
let sixthLetter = document.get("6") as! String
let seventhLetter = document.get("7") as! String
let eighthLetter = document.get("8") as! String
let organizedData = [firstLetter, secondLetter, thirdLetter, fourthLetter, fifthLetter, sixthLetter, seventhLetter, eighthLetter]
self.databaseData = organizedData
self.make2darray.append(self.databaseData) //makes 2darray
} //closing fetch
dispatchGroup.leave()
} //closing else statement
//print("printing", self.make2darray) // works ok
dispatchGroup.notify(queue: .main){
completion(self.make2darray)
}
} //closing snapshot
} //closing function
you may be calling the print statement too early, try calling it after the function has run
override func viewdidload() {
super.viewDidLoad()
readFromDatabase()
print (make2darray)
}
Okay to anyone looking for an answer to this in the future. Here is how I was able to resolve the issue. I had to create an observer with a notification key.
The notification key was created as a variable right under the class declaration like this: let notificationKey = "#####". The hash would be a unique key like your website name or something.
In the viewDidLoad, I called createObserver() and called readFromDatabase() right after.
What is happening is that while the readFromDatabase() function is getting the data, the observer is waiting to hear a signal from the Notification Center. When the work is complete the Notification Center triggers the key we created that tells the observer the process is complete. That means now that "make2darray" has the complete data records pulled from the database and is ready for use. We must also remove the observer right after to not cause any issues later.
This code is crude I'm sure, but it works. I'm a new programmer, so use whatever way gets the same results for you. Good luck to you.
func createObserver(){
//listening to hear if the database call is complete
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.readFromDatabase), name: NSNotification.Name(rawValue: notificationKey), object: nil)
}
#objc func readFromDatabase() {
let db = Firestore.firestore()
db.collection("Letters").getDocuments { (snapshot, error) in
if error != nil {
print(error?.localizedDescription ?? "error")
} else {
for document in (snapshot?.documents)! {
// let fetchedData = document.data() //gets all data from all documenters unorganized
let firstLetter = document.get("1") as! String
let secondLetter = document.get("2") as! String
let thirdLetter = document.get("3") as! String
let fourthLetter = document.get("4") as! String
let fifthLetter = document.get("5") as! String
let sixthLetter = document.get("6") as! String
let seventhLetter = document.get("7") as! String
let eighthLetter = document.get("8") as! String
let organizedData = [firstLetter, secondLetter, thirdLetter, fourthLetter, fifthLetter, sixthLetter, seventhLetter, eighthLetter]
self.databaseData = organizedData
self.make2darray.append(self.databaseData) //makes 2darray
} //closing fetch
} //closing else statment
//Notifies the observer that the process is complete
NotificationCenter.default.post(name: NSNotification.Name(rawValue: self.notificationKey), object: nil)
//Removing the observer
NotificationCenter.default.removeObserver(self)
} //closing database call
//verifying all entries are present
//print("printing outside database call", self.make2darray.count)
} //closing function

How we can find an element from [AnyObject] type array in swift

I have [AnyObject] array
var updatedPos = [AnyObject]()
I am setting data in that according to my requirement like!
let para:NSMutableDictionary = NSMutableDictionary()
para.setValue(posId, forKey: "id")
para.setValue(posName, forKey: "job")
let jsonData = try! NSJSONSerialization.dataWithJSONObject(para, options: NSJSONWritingOptions())
let jsonString = NSString(data: jsonData, encoding: NSUTF8StringEncoding) as! String
self.updatedPos.append(jsonString)
Now in my code i have some requirement to remove the object from this array where id getting matched according to requirement Here is the code which i am trying to implement
for var i = 0; i < updatedPos.count; i++
{
let posItem = updatedPos[i]
print("Id=\(posItem)")
let pId = posItem["id"] as? String
print("secRId=\(pId)")
if removeId! == pId!
{
updatedPos.removeAtIndex(i)
}
}
Here print("Id=\(posItem)") give me output asId={"id":"51","job":"Programmer"} but here i am not able to access id from this object. here print("secRId=\(pId)") give me nil
First of all use native Swift collection types.
Second of all use types as specific as possible.
For example your [AnyObject] array can be also declared as an array of dictionaries [[String:AnyObject]]
var updatedPos = [[String:AnyObject]]()
Now create the dictionaries and add them to the array (in your example the dictionary is actually [String:String] but I keep the AnyObject values).
let para1 : [String:AnyObject] = ["id" : "51", "job" : "Programmer"]
let para2 : [String:AnyObject] = ["id" : "12", "job" : "Designer"]
updatedPos.append(para1)
updatedPos.append(para2)
If you want to remove an item by id use the filter function
let removeId = "12"
updatedPos = updatedPos.filter { $0["id"] as? String != removeId }
or alternatively
if let indexToDelete = updatedPos.indexOf{ $0["id"] as? String == removeId} {
updatedPos.removeAtIndex(indexToDelete)
}
The JSON serialization is not needed for the code you provided.
PS: Never write valueForKey: and setValue:forKey: unless you know exactly what it's doing.
After some little bit stuffs I have found the very easy and best solution for my question. And I want to do special thanks to #vadian. Because he teach me new thing here. Hey Thank you very much #vadian
Finally the answer is I had covert posItem in json Format for finding the id from Id={"id":"51","job":"Programmer"} this string
And the way is
let data = posItem.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
if let dict = json as? [String: AnyObject] {
let id = dict["id"]
if removeId! == id! as! String
{
updatedLoc.removeAtIndex(i)
}
}
}
catch {
print(error)
}

How to convert from a Swift String Set to an Array

I am trying to create an array of words from a string object retrieved from Parse. The object retrieved looks like this:
Then this line of code gives this.
let joinedWords = object["Words"] as! String
How do I convert joinedWords to an Array?
If you don't care about the order, you can use flatMap on the set:
var mySet = Set<String>()
for index in 1...5 {
mySet.insert("testwords\(index)")
}
let myArray = mySet.flatMap { $0 }
print(myArray) // "["testwords5", "testwords3", "testwords4", "testwords2", "testwords1"]"
If you want the list sorted alphabetically, you can make your array a var and use sortInPlace()
var myArray = mySet.flatMap { $0 }
myArray.sortInPlace()
print(myArray) // "["testwords1", "testwords2", "testwords3", "testwords4", "testwords5"]"
If object["Words"] is AnyObject, you will have to unwrap it.
if let joinedWordsSet = object["Words"] as? Set<String> {
var joinedWordsArray = joinedWordsSet.flatMap { $0 }
myArray.sortInPlace()
print(myArray)
}
Swift 3 note: sortInPlace() has been renamed sort().
Many thanks to #JAL for so much time on chat to solve this one. This is what we came up with. Its a bodge and no doubt there is a better way!
When uploading to Parse save the set as an array.
let wordsSet = (wordList?.words?.valueForKey("wordName"))! as! NSSet
let wordsArray = Array(wordsSet)
Then it saves to Parse - looking like a set, not an array or a dictionary.
let parseWordList = PFObject(className: "WordList")
parseWordList.setObject("\(wordsArray)", forKey: "Words")
parseWordList.saveInBackgroundWithBlock { (succeeded, error) -> Void in
if succeeded {
// Do something
} else {
print("Error: \(error) \(error?.userInfo)")
}
}
Then you can drop the [ ] off the string when its downloaded from Parse, and remove the , and add some "" and voila, there is an array that can be used e.g. to add to CoreData.
var joinedWords = object["Words"] as! String
joinedWords = String(joinedWords.characters.dropFirst())
joinedWords = String(joinedWords.characters.dropLast())
let joinedWordsArray = joinedWords.characters.split() {$0 == ","}.map{ String($0) } // Thanks #JAL!

Swift Array: [AnyObject] vs. [xxxxxxxxClass] and the method "append"

Here's my code. You don't need to look at all of it. I added comments where I'm confused:
class ProductData: NSObject {
var title = ""
var icon = ""
private init(dict: NSDictionary){
title = dict["title"] as! String
icon = dict["icon"] as! String
super.init()
}
class func getTheData(fromJSONPath JSONPath: String) -> [ProductData] {
let JSONData = NSData(contentsOfFile: JSONPath)!
var JSONArray = [[String : AnyObject]]()
do {
JSONArray = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions.MutableContainers) as! [Dictionary]
} catch { print("error")}
-----------------------------------------------------------------------------------------
//↓↓↓↓↓↓↓↓↓ different: data = "[AnyObject]()" or "[ProductData]()" ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
var data = [AnyObject]()
// var data = [ProductData]()
for d in JSONArray {
data.append(ProductData(dict: d))
}
return data as! [ProductData]
// return data
//↑↑↑↑↑↑↑↑↑ and here: return "data as! [ProductData]" or "data" ↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
}
I use "var data = [ProductData](), retun data" first. There's no error or warning, but when I run my app, and run to the code data.append(ProductData(dict: d)), it crashes with the error: thread 1:exc_bad_access(code=1,address=0x10). What?!
I found a way to fix it: if I use var datas = [AnyObject]() and return datas as! [ProductData], it works very well.
I am so confused:
Why does [AnyObject] make the code OK?
When I use [ProductData], why does the code: data.append(ProductData(dict: d)) crash?
What is the different between [AnyObject] and [ProductData]?
Your original version works for me (screenshot) (only slightly modified for testing with my data). You shouldn't have to do this dance, something else is causing trouble.
I suggest cleaning up your class a bit and take advantage of Swift 2 using guard, map and error. It will be easier to debug and will work more efficiently anyway.
Here's an example. The only difference is that I'm using NSURL to access the data in my case and I've removed the icon value, but it's easy to change it back to your case.
class ProductData: NSObject {
var title = ""
private init(dict: [String : AnyObject]){
if let t = dict["title"] as? String { self.title = t }
super.init()
}
class func getTheData(fromJSONPath JSONPath: String) -> [ProductData] {
do {
// safely unwrap and typecast the values else return empty array
guard let url = NSURL(string: JSONPath),
let JSONData = NSData(contentsOfURL: url),
let JSONArray = try NSJSONSerialization.JSONObjectWithData(JSONData, options: [])
as? [[String : AnyObject]] else { return [] }
return JSONArray.map() { ProductData(dict: $0) }
} catch {
// this `error` variable is created by the `catch` mechanism
print(error)
// return empty array if unkown failure
return []
}
}
}
let test = ProductData.getTheData(fromJSONPath: "http://localhost:5678/file/test.json")
Note: I'm sure you know it but just in case for the readers, NSData(contentsOf... is a synchronous function, so it will block the main thread (unless executed from a background thread). It's better practice to use asynchronous functions when possible.

Resources