why is only one field being displayed? - swift

Trying to print out the names of all the current users of the application, they print in the console however when the app runs only one of the names is displayed.
func getData() {
let db = Firestore.firestore()
// Get data
db.collection("users").getDocuments()
{
(snapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
snapshot!.documents.forEach({ (document) in
let firstname = document.data()["firstname"]
print(firstname!)
self.studentlbl.text = (firstname! as! String)
}
)
How can this be fixed?
Thanks!

It looks like you only have one label view, so each time this line runs you overwrite the previous value of that label:
self.studentlbl.text = (firstname! as! String)
If you want to show all values in that single label, you can do:
self.studentlbl.text = self.studentlbl.text + (firstname! as! String)

Related

How to merge two queries using Firestore - Swift

I need to merge two queries with firebase firestore and then order the results using the timestamp field of the documents.
Online I didn't find much information regarding Swift and Firestore.
This is what I did so far:
db.collection("Notes").whereField("fromUid", isEqualTo: currentUserUid as Any).whereField("toUid", isEqualTo: chatUserUid as Any).getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
return
}
db.collection("Notes").whereField("fromUid", isEqualTo: self.chatUserUid as Any).whereField("toUid", isEqualTo: self.currentUserUid as Any).getDocuments { (snaphot1, error1) in
if let err = error1{
print(err.localizedDescription)
return
}
}
}
I added the second query inside the first one on completion but now I don't know how to merge them and order them through the field of timestamp.
On this insightful question It is explained that it's recommended to use a Task object but I don't find anything similar with swift.
There are many ways to accomplish this; here's one option.
To provide an answer, we have to make a couple of additions; first, we need somewhere to store the data retrieved from firebase so here's a class to contains some chat information
class ChatClass {
var from = ""
var to = ""
var msg = ""
var timestamp = 0
convenience init(withDoc: DocumentSnapshot) {
self.init()
self.from = withDoc.get("from") as! String
self.to = withDoc.get("to") as! String
self.msg = withDoc.get("msg") as! String
self.timestamp = withDoc.get("timestamp") as! Int
}
}
then we need a class level array to store it so we can use it later - perhaps as a tableView dataSource
class ViewController: NSViewController {
var sortedChatArray = [ChatClass]()
The setup is we have two users, Jay and Cindy and we want to retrieve all of the chats between them and sort by timestamp (just an Int in this case).
Here's the code that reads in all of the chats from one user to another creates ChatClass objects and adds them to an array. When complete that array is passed back to the calling completion handler for further processing.
func chatQuery(from: String, to: String, completion: #escaping( [ChatClass] ) -> Void) {
let chatsColl = self.db.collection("chats") //self.db points to my Firestore
chatsColl.whereField("from", isEqualTo: from).whereField("to", isEqualTo: to).getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
var chatArray = [ChatClass]()
for doc in docs {
let chat = ChatClass(withDoc: doc)
chatArray.append(chat)
}
completion(chatArray)
})
}
Then the tricky bit. The code calls the above code which returns an array The above code is called again, returning another array. The arrays are combined, sorted and printed to console.
func buildChatArray() {
self.chatQuery(from: "Jay", to: "Cindy", completion: { jayCindyArray in
self.chatQuery(from: "Cindy", to: "Jay", completion: { cindyJayArray in
let unsortedArray = jayCindyArray + cindyJayArray
self.sortedChatArray = unsortedArray.sorted(by: { $0.timestamp < $1.timestamp })
for chat in self.sortedChatArray {
print(chat.timestamp, chat.from, chat.to, chat.msg)
}
})
})
}
and the output
ts: 2 from: Cindy to: Jay msg: Hey Jay, Sup.
ts: 3 from: Jay to: Cindy msg: Hi Cindy. Not much
ts: 9 from: Jay to: Cindy msg: Talk to you later

Struggling To Query Using getDocuments() in Firestore Swift

This is the first time I am using a Firestore Query and I'm struggling to parse the data. I normally use the same setup when I get documents (which works), but when I attach it to a query it does not work.
I am trying to query the database for the shop most visited, so I can later set it as favourite.
My Code:
func findFavouriteShop(completed: #escaping ([String]) -> Void)
{
// Variables
let dispatch = DispatchGroup()
var dummyDetails = [String]()
// References
let db = Firestore.firestore()
let userID = Auth.auth().currentUser?.uid
let groupCollectionRef = String("visits-" + userID! )
// Query the database for the document with the most counts
dispatch.enter()
db.collectionGroup(groupCollectionRef).order(by: "count", descending: true).limit(to: 1).getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching documents: \(err)")
}
else {
print(snapshot)
guard let snap = snapshot else {return}
for document in snap.documents {
let data = document.data()
// Start Assignments
let shopName = data["shopName"] as? String
let count = data["count"] as? String
// Append the dummy array
dummyDetails.append(shopName!)
dummyDetails.append(count!)
}
dispatch.leave()
}
dispatch.notify(queue: .main, execute: {
print("USER number of documents appended: \(dummyDetails.count)")
completed(dummyDetails)}
)
}
Using Print statements it seems as if the guard statement kicks the function out. The processor does not reach the for-loop to do the assignments. When I print the snapshot it returns an empty array.
I am sure I have used the wrong notation, but I'm just not sure where.
There's a lot to comment on, such as your choice of collection groups over collections (maybe that's what you need), why you limit the results to one document but feel the need to query a collection, the naming of your collections (seems odd), the query to get multiple shops but creating a function that only returns a single shop, using a string for a count property that should probably be an integer, and using a string array to return multiple components of a single shop instead of using a custom type.
That said, I think this should get you in the right direction. I've created a custom type to show you how I'd start this process but there's a lot more work to be done to get this where you need it to be. But this is a good starting point. Also, there was no need for a dispatch group since you weren't doing any additional async work in the document parsing.
class Shop {
let name: String // constant
var count: Int // variable
init(name: String, count: Int) {
self.name = name
self.count = count
}
}
func findFavouriteShops(completion: #escaping (_ shops: [Shop]?) -> Void) {
guard let userID = Auth.auth().currentUser?.uid else {
completion(nil)
return
}
var temp = [Shop]()
Firestore.firestore().collection("visits-\(userID)").order(by: "count", descending: true).limit(to: 1).getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
if let error = error {
print(error)
}
completion(nil)
return
}
for doc in snapshot.documents {
if let name = doc.get("shopName") as? String,
let count = doc.get("count") as? String {
let shop = Shop(name: name, count: count)
temp.append(Shop)
}
}
completion(temp)
}
}
You can return a Result type in this completion handler but for this example I opted for an optional array of Shop types (just to demonstrate flexibility). If the method returns nil then there was an error, otherwise there are either shops in the array or there aren't. I also don't know if you're looking for a single shop or multiple shops because in some of your code it appeared you wanted one and in other parts of your code it appeared you wanted multiple.
findFavouriteShops { (shops) in
if let shops = shops {
if shops.isEmpty {
print("no error but no shops found")
} else {
print("shops found")
}
} else {
print("error")
}
}

Swift + Firebase. Accessing current user's document

My current firebase structure is Collection of Users which then have a subcollection of habits. For a given user, I want them to be able to add to their own collection of routines. however, running into an issue. When I run the function below, it just creates a separate user with a separate routine. How would I tie a new routine to a current authenticated user?
func addData(routineMsg: String){
let db = Firestore.firestore()
let user = db.collection("users").document()
let routine = db.collection("users").document("GzsHAHq1P0uXGdlYwF8P").collection("routines").document()
routine.setData(["id": routine.documentID, "routine": routineMsg]) { err in
if err != nil {
print((err?.localizedDescription)!)
return
}
}
}
Right now, the code shows how I hard-code it to a certain document (GzsHAHq1P0uXGdlYwF8P), but would like to be able to determine the document dynamically by user
let user = db.collection("users").document()
By not passing document() an argument, what you are doing is creating a new document reference with an auto-generated document ID. What you want to do is pass the method with a string that locates the user's document. Ideally, this would be the user's ID:
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let userDocRef = db.collection("users").document(uid)
From there, to generate random document IDs in the subcollection, do what you were doing before:
func addData(routineMsg: String) {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let db = Firestore.firestore()
let userDocRef = db.collection("users").document(uid)
let routineDocRef = userDocRef.collection("routines").document()
routineDocRef.setData([
"id": routineDocRef.documentID,
"routine": routineMsg
]) { error in
if let error = error {
print(error)
}
}
}

How to call combine multiple completion handlers to combine data to one new array

I have been stuck for a while now and any advice would be greatly appreciated. I am creating an app that uses Firebase database and I have created 5 classes that hold different data in Firebase. I'm creating a tableview that needs to display information from each of the 5 classes (Profile name, image, then information about a league, and info about scores). So in my new class I created a function calling for data from firebase from each class...
For example: GET all players from X league {
FOR every player in the league {
GET the players information
THEN GET the scores
THEN on and on
once we have all information APPEND to new array
}
and then rank the array
}
After all this runs I want to reload the table view on the VC
SO my solution works on the original load but if I back out and re enter the screen the names and images repeat.
To be exact when the indexes print to the console I get
"Player 1: Zack"
"Player 2: John"
However, the screen shows John's image and name repeatedly. BUT only that class... All other data stays where it is supposed to be. And the original functions are all written the same way.
I'm thinking it's something to do with memory management or I wrote my completion handler poorly?
Here is the code in the new array class:
You'll also notice that my completion() is inside my for in loop which I HATE but it's the only way I could get the function to finish before completing.. Otherwise the function completes before the data is ready.
func getLeaderboard(leagueID: String, completion: #escaping ()->()) {
print("League Count After removeAll \(self.rankedGolfers.count)")
self.leagueMembers.getLeagueMembers(leagueID: leagueID) {
print("HANDLER: Step 1: Get League Members")
for member in self.leagueMembers.leagueMembers {
print("Golfer Member ID: \(member.userID)")
self.golferInfo.getGolferInfo(userKey: member.userID, completion: {
print("HANDLER: Step 2: Get player profile info")
print("Golfer Name3: \(self.golferInfo.golfers[0].firstName) \(self.golferInfo.golfers[0].lastName)")
self.handicapHelper.getRounds(userID: member.userID, completion: {
print("HANDLER: Step 3: Get players Handicap")
print("Golfer Handicap3: \(self.golferInfo.golfers[0].lastName): \(self.handicapHelper.handicap)")
self.leagueInfo.getLeagueInfo(leagueID: leagueID, completion: {
print("HANDLER: Step 4: Get league info")
let golferIndex = self.golferInfo.golfers[0]
let memberInfoIndex = self.leagueInfo.leagueInfo[0]
let golferID = member.userID
let profileImg = golferIndex.profileImage
let golferName = "\(golferIndex.firstName) \(golferIndex.lastName)"
let handicap = self.handicapHelper.handicap
let golferLeaguePardieScore = member.pardieScore
let leagueRoundsPlayed = member.numberOfRounds
let roundsRemaining = memberInfoIndex.leagueMinRounds - leagueRoundsPlayed
let currentWinnings = member.currentWinnings
let newGolfer = Leaderboard(golferID: golferID, profileImg: profileImg ?? "No Img", golferName: golferName, golferHandicap: handicap, golferLeaguePardieScore: golferLeaguePardieScore, roundsPlayedInLeague: leagueRoundsPlayed, roundsRemaining: roundsRemaining, currentWinnings: currentWinnings)
self.rankedGolfers.append(newGolfer)
print("HANDLER: Step 5: Add golfer to array")
//print("Golfer Name 4: \(newGolfer.golferName)")
//print("Rounds Remaining: \(newGolfer.roundsRemaining)")
print("league Member Count: \(self.rankedGolfers.count)")
self.getLeaderboardRanking()
print("HANDLER: Step 6: Rank Array")
//print("COMPLETION: \(self.rankedGolfers.count)")
completion()
})
})
})
}
}
}
Thank you for any help possible!
I think we can solve this with a DispatchGroup, which will ensure all of the data is loaded for each user, then append the user to an array used as a tableView dataSource and then reload the tableView upon completion.
To keep it simple we'll start with a UserInfo class which stores their uid, name, favorite food and handicap.
class UserInfoClass {
var uid = ""
var name = ""
var favFood = ""
var handicap = 0
}
and a class var array used as the dataSource for the tableView
var userInfoArray = [UserInfoClass]()
Then, assuming we have a structure like this...
users
uid_0
name: "Leroy"
handicaps
uid_0
amt: 4
fav_foods
uid_0
fav_food: "Pizza"
...here's a function that reads all users, then iterates over each one populating a UserInfoClass with their name and uid, as well as creating a dispatch group that also populates their favorite food and handicap. When that's complete the user is added to the dataSource array and when all of the users are read the tableView is reloaded to display the information.
func loadUsersInfoAndHandicap() {
let ref = self.ref.child("users")
self.userInfoArray = []
ref.observeSingleEvent(of: .value, with: { snapshot in
let group = DispatchGroup()
let allUsers = snapshot.children.allObjects as! [DataSnapshot]
for user in allUsers {
let uid = user.key
let name = user.childSnapshot(forPath: "name").value as? String ?? "No Name"
let aUser = UserInfoClass()
aUser.uid = uid
aUser.name = name
group.enter()
self.loadFavFood(withUid: uid) {favFood in
aUser.favFood = favFood
group.leave()
}
group.enter()
self.loadHandicap(withUid: uid) { handicap in
aUser.handicap = handicap
group.leave()
}
group.notify(queue: .main) {
self.userInfoArray.append(aUser)
}
}
group.notify(queue: .main) {
print("done, reload the tableview")
for user in self.userInfoArray {
print(user.uid, user.name, user.favFood, user.handicap)
}
}
})
}
the users name and uid is read from the main users node and here are the two functions that read their favorite food and handicap.
func loadFavFood(withUid: String, completion: #escaping(String) -> Void) {
let thisUser = self.ref.child("userInfo").child(withUid)
thisUser.observeSingleEvent(of: .value, with: { snapshot in
let food = snapshot.childSnapshot(forPath: "fav_food").value as? String ?? "No Fav Food"
completion(food)
})
}
func loadHandicap(withUid: String, completion: #escaping(Int) -> Void) {
let thisUser = self.ref.child("handicaps").child(withUid)
thisUser.observeSingleEvent(of: .value, with: { snapshot in
let handicap = snapshot.childSnapshot(forPath: "amt").value as? Int ?? 0
completion(handicap)
})
}
note that self.ref points to my firebase so substitute a reference to your firebase.
Note I typed this up very quickly and there is essentially no error checking so please add that accordingly.

Display Firestore data into UIPicker Swift 4

I am still new in swift development, my problem is, I have Firestore structure as below:
the problem is to display the list of title from firestore into a uipicker, I need to get data into an array like below:
[firsProgramme, secondProgramme, thirdProgramme]
I managed to display all the "title" from firestore in the string, not in the array
below is the code:
func getprogram() {
let db = Firestore.firestore()
db.collection("Programme").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
for document in querySnapshot!.documents {
let data = document.data()
let program = data["title"] as? String ?? ""
// let agencyId = document.documentID
print(program)
//print("\(document.documentID) => \(document.data())");
}
}
}
}
result print(program) return as below :
firstprogramme
secondprogramme
thirdprogramme
the other part for UIPicker is already being managed well.
Thanks in advance.
In you class create an array variable to hold information about your programs:
var programsArray: [String] = []
When reading data from Firebase, instead of print(program) use programsArray.append(program). Call UIPickerView reload function after you have all data.
DispatchQueue.main.async {
self.your_picker_view.reloadAllComponents()
}
And of course, use this array inside your pickerview datasource methods.