Swift: Unable to append struct instance to array - arrays

I have been having some trouble creating a temporary array of user data from Firestore. Basically I created a function that retrieves user data from a Firestore collection and then iterates through each document within that collection, creating an instance of my "Thought" struct for each one. I then append each "Thought" instance to a temporary array called "tempThoughts", and the function then returns that array. The problem is that nothing seems to be appended to the array in the function. When I test it by printing out the contents of the array upon completion, it just prints an empty array.
The data itself is being read from the Firestore collection as it prints out each document the function iterates through, so I don't think that is the problem. I also tried checking to see if I am actually creating instances of the "Thought" struct properly by printing that out, and that seemed to be working. Does anyone have any idea what's wrong with the way I am appending the struct instances to the array? Perhaps there is a better way to go about doing this? Thanks for any help in advance.
Here is my current function:
func getUserDocuments() -> [Thought]{
var tempThoughts = [Thought]()
db.collection(cUser!.uid).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
let tempThought: Thought = Thought(id: document.get("id") as! String, content: document.get("content") as! String, dateCreated: document.get("timestamp") as! String, isFavorite: (document.get("isFavorite") != nil))
tempThoughts.append(tempThought)
}
}
}
print("TEST")
print(tempThoughts)
return tempThoughts
}

Your getDocuments is an asynchronous operation. And you've updated your tempThoughts in it's completion only. But the place where you've printed it out will get executed before the getDocuments completion. Check out the order of results logged in the console.

You need to update your code like this
func getUserDocuments(_ onSuccess: ((_ thoughts: [Thought] ) -> Void), onFailuer: ((_ error: String) -> Void)) {
var tempThoughts = [Thought]()
db.collection(cUser!.uid).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
onFailuer(err)
} else {
DispatchQueue.main.async {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
let tempThought: Thought = Thought(id: document.get("id") as! String, content: document.get("content") as! String, dateCreated: document.get("timestamp") as! String, isFavorite: (document.get("isFavorite") != nil))
tempThoughts.append(tempThought)
}
print("TEST")
print(tempThoughts)
onSuccess(tempThoughts)
}
}
}
}
user this code
And you can use this function like this
getUserDocuments({ (thoughts) in
// Your logic
}) { (error) in
// error Occured
}

Related

Issue with JSON Decoding? / How can I debug this?

I'm making an API call and managing the data received, but my call is catching an error. Here's my getData() code:
func getData(from url: String) {
URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong.")
return
}
do {
self.instructionsResults = try JSONDecoder().decode([Step].self, from: data)
print("getData() was successful!")
print(self.instructionsResults)
} catch {
print("Decoding error:")
print(String(describing: error)) // <-- this pings
}
}).resume()
}
Here's a pastebin of an example url json data: link
And here's the struct I've defined for this fetch:
struct Step: Codable {
let number: Int
let step: String?
}
This may be extra, but I'm using the call above to populate the array instantiated as var steps: [String] = [] with the step: String data of each step in the JSON Step array.
for n: Int in 0 ..< instructionsResults.count {
if instructionsResults[n].step != nil {
let step = instructionsResults[n].step ?? "n/a"
print("step: \(instructionsResults[n].step)")
print("step: \(step)")
steps.append(step)
}
}
print("Steps: \(steps)")
}
Does anyone have any insight on what's going wrong? My final print statement always returns as empty. I've done a similar type of call formatted a similar way earlier in this project, and that worked completely fine, so I'm stumped as to where I went wrong with this one. Any insight / feedback would be greatly appreciated, thank you.
Edit: Here's the error code:
Steps: []
Decoding error:
keyNotFound(CodingKeys(stringValue: "number", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"number\", intValue: nil) (\"number\").", underlyingError: nil))
The error says that there is no key number in the top level object.
Please read the JSON carefully. You are ignoring the object on the root level, the array with the key steps.
You need this
struct Root: Decodable {
let steps: [Step]
}
struct Step: Decodable {
let number: Int
let step : String
}
and decode
.decode([Root].self,

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]

Can I update a global array using a for-in loop, and use the updated array outside of the loop ? (Swift)

I am currently fetching ImageUrls from Firestore, and accessing them using a for-in loop. Within the for-in loop, I append to a global array using .append(). However, when I call the array outside of the for-in loop, it is empty. I read the response to this post 'IOS Swift value of an Integer is not being saved' which was helpful in understanding that my global array is empty because the for-in loop is being executed after I call my array, and how to overcome this problem when using a closure - but how can I achieve the same result when using a for-in loop ? Any direct help or links to helpful articles/videos/posts will be much appreciated.
I know there are similar posts to this, but I went through them and there was only the one I referenced above referring to this problem in swift, so I read that but still struggling. Will attach code below:
// global array of strings which I want to append to:
var tuwoImageUrls = [String]()
internal var tuwos: [Tuwo]? {
didSet {
self.pickerView.pickerView.reloadData()
}
}
// function fetching documents ('tuwos') from firestore:
func fetchTuwos(completion handler: #escaping ([Tuwo]?, Error?) -> ()) {
guard let uid = Auth.auth().currentUser?.uid else {
return handler(nil, nil)
}
Firestore.firestore()
.collection("tuwos").document(uid).collection("tuwos")
.getDocuments { (querySnapshot, error) in
handler(querySnapshot?.documents.compactMap({
Tuwo(dictionary: $0.data())
}), error)
}
}
// calling function, accessing documents to append ImageUrl to global array ('tuwoImageUrls') using for-in loop:
fetchTuwos { [weak self] (tuwos, error) in
self?.tuwos = tuwos
DispatchQueue.main.async {
for tuwo in self!.tuwos! {
let tuwoImageUrl = "\(tuwo.profileImageUrl)"
self!.tuwoImageUrls.append("\(tuwoImageUrl)")
self!.pickerView.pickerView.reloadData()
}
}
}
// calling a print statement of the array outside of the scope of fetchTuwos returns an empty array
print("The Urls:" , tuwoImageUrls)
// my 'Tuwo' struct
struct Tuwo {
let name, profileImageUrl, uid: String
init(dictionary: [String: Any]) {
self.name = dictionary["name"] as? String ?? ""
self.profileImageUrl = dictionary["profileImageUrl"] as? String ?? ""
self.uid = dictionary["uid"] as? String ?? ""
}}
I hope this solution helps
fetchTuwos { [weak self] (tuwos, error) in
self?.tuwos = tuwos
self?.tuwoImageUrls = tuwos?.map { "\($0.profileImageUrl)" } ?? []
DispatchQueue.main.async { [weak self] in
self?.pickerView.pickerView.reloadData()
}
print("\(tuwoImageUrls)")
}

Why have a Array Empty and Error in swift4?

I've spent a few hours trying to get a fetch to work in my film sheet. I need to open film's View of my colectionview Items. I could follow different guide and post but it always give me an empty array. I'm newbie, sorry for my question but I need your help.
Here's my code:
var taskArrayScheda : NewFilm?
override func viewDidLoad() {
super.viewDidLoad()
self.fetchData()
if(self.taskArrayScheda != nil) {
let schedaOk = taskArrayScheda
mostraDatiNellaScheda(schedaOk!)
} else { print("errore array vuoto") }
}
func mostraDatiNellaScheda(_ sched:NewFilm) {
// get title
titoloScheda.text = sched.titolo
}
func fetchData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "NewFilm")
do {
taskArrayScheda = try context.fetch(NewAnime.fetchRequest())
đŸ’¥ERROR ::::: Cannot assign value of type '[Any]' to type 'NewFilm?'
} catch {
print(error)
}
}
The fetch() returns an array. But currently you assign the fetch() result to the single object var taskArrayScheda.
You'll need something like:
var taskArrayScheda: [NewFilm]?
Then you should do:
taskArrayScheda = try context.fetch(NewAnime.fetchRequest()) as? [NewFilm]
I assume here that NewAnime is a subclass of NewFilm, which seems to make sense looking at these two class names.

Load Firestore data to Table view Swift 4

I have a question related to load data from Firestore to table view. Basically, i understand how to do it, but in this case, i kind of confuse if the structure of Firestore as below:
"fund" -> "randomId" -> "agensi: id"
i was able to load the agensi from collection which is "WJ18kBvDLhVrvml2slXB". To get the real agensi name i have to get data from collection "Agensi" as image below:
below is the code that what i have already did:
var agencyname: [String] = []
func getData() {
db.collection("fund").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
// var agencyNumber = 0
for document in querySnapshot!.documents {
let data = document.data()
let agency = data["agensi"] as? String ?? ""
let agencyId = document.documentID
print(agency)
print(agencyId)
//MARK get name
let newdocRef = Firestore.firestore().document("Agensi/\(agency)")
newdocRef.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else { return }
let dataDetails = docSnapshot.data()
let agencyNew = dataDetails!["name"] as? String ?? ""
self.agencyname.append(agencyNew)
print("List of the agency: \(self.agencyname.append(agencyNew))")
}
}
self.tableView.reloadData()
}
}
}
i try to print:
self.agencyname.append(agencyNew)
but its display nothing. so, i cannot load the name of the agency into my table view cell. By the way sorry for my bad english. Thanks in advance
There are few things I would like to tell:
1. FireStore queries are asynchronous so there is a callback function when it finishes. Reload your tableView inside the callback instead of end of the loop only then it will load the data properly.
DispatchQueue.main.async {
self.tableView.reloadData()
}
Write above code just below print("List of the agency: \(self.agencyname.append(agencyNew))") line.
2. You're printing "self.agencyname.append(agencyNew)" and it is void by the way so will not print anything so print(\(self.agencyname)) instead.
3. When using a guard in a loop then don't use return because it will break the loop for next iteration if any error occurs. We should use continue here to let the loop execute completely.

Resources