I'm trying to create an array of structs by creating a function to fetch data from firestore and then passing the three structs fetched to an array of structs. Here is my code:
func fetchUsers() {
var user1: User
var user2: User
var user3: User
docRef = Firestore.firestore().document("users/user1")
docRef.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else { return }
let myData = docSnapshot.data()
let fName = myData!["name"] as? String ?? ""
let fUsername = myData!["username"] as? String ?? ""
let fBioText = myData!["bioText"] as? String ?? ""
let user = User(name: fName, username: fUsername, bioText: fBioText, profileImage: #imageLiteral(resourceName: "mauricioprofileimage"))
user1 = user
}
self.docRef = Firestore.firestore().document("users/user2")
self.docRef.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else { return }
let myData = docSnapshot.data()
let fName = myData!["name"] as? String ?? ""
let fUsername = myData!["username"] as? String ?? ""
let fBioText = myData!["bioText"] as? String ?? ""
let user = User(name: fName, username: fUsername, bioText: fBioText, profileImage: #imageLiteral(resourceName: "trumpprofileimage"))
user2 = user
}
self.docRef = Firestore.firestore().document("users/user1")
self.docRef.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else { return }
let myData = docSnapshot.data()
let fName = myData!["name"] as? String ?? ""
let fUsername = myData!["username"] as? String ?? ""
let fBioText = myData!["bioText"] as? String ?? ""
let user = User(name: fName, username: fUsername, bioText: fBioText, profileImage: #imageLiteral(resourceName: "amandaprofileimage"))
user3 = user
}
return [user1, user2, user3]
}
let users: [User] = fetchUsers()
The problem I'm getting is that when I try to fill my users array, it says that it doesn't recognize the user1, user2 and user3 that I created in the Fetchfuncion.
Ps: I'm using firestore. Thank you for all the help!
Put each request in a group, and only once all three users have been retrieved, return the array of users. What's happening with your code is that it's returning an array of empty users (or perhaps 1-3 inconsistently), due to the fact that these calls are asynchronous so the data returned from a firebase request isn't guaranteed to exist when the array is returned.
let group = DispatchGroup()
group.enter()
make_async_request_1 {
// Process response
group.leave()
}
group.enter()
make_async_request_2 {
// Process response
group.leave()
}
group.enter()
make_async_request_3 {
// Process response
group.leave()
}
group.notify(queue: .main) {
// This will run after all 3 group.leave() calls are made
}
Related
I want to write the data that I received from the request into an array and then display it through a list
Here is my structure for the json file
struct DataRespons: Codable {
let data: [String]
let status: String
}
struct UserRespons: Codable {
let status: String
let data: UserData
}
struct UserData: Codable, Identifiable {
let id: String
let firstName: String
let lastName: String
let age: Int
let gender: String
let country: String
}
This is my class for JSON requests and decoding
import Foundation
#MainActor
class NetworkModel: ObservableObject {
#Published var listId: [String] = []
var statusList = ""
var statusUser = ""
var temp = ""
var user: [UserData] = [] // here I am not sure if this type Array
#Published var userData = UserRespons(status: "??", data: UserData(id: "???", firstName: "???", lastName: "??", age: 4, gender: "???", country: "???"))
this func for receive a letter with links to which I should make requests
func getList() {
guard let url = URL(string: "https://opn-interview-service.nn.r.appspot.com/list") else { fatalError("Missing URL") }
var request = URLRequest(url: url)
request.addValue("bearer \(token)", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Requst error",error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async { [self] in
do {
let decoded = try JSONDecoder().decode(DataRespons.self, from: data)
self.listId = decoded.data
self.statusList = decoded.status
for i in self.listId.indices {
print("This is id[\(i)] = \(listId[i])")
getUser(url: "\(listId[i])")
// #MARK: NEED HERE HELP user.append(.init(id: <#T##String#>, firstName: <#T##String#>, lastName: <#T##String#>, age: <#T##Int#>, gender: <#T##String#>, country: <#T##String#>))
}
} catch let error{
print("Error decode",error)
}
}
}
}
dataTask.resume()
}
I want to add data that will come from requests to an empty array so that it can then be displayed in the list
function for decoding data user
func getUser(url: String) {
guard let url = URL(string: "https://opn-interview-service.nn.r.appspot.com/get/\(url)") else { fatalError("Missing URL") }
var request = URLRequest(url: url)
request.addValue("bearer \(token)", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Requst error",error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async { [self] in
do {
let decoded = try JSONDecoder().decode(UserRespons.self, from: data)
self.userData = decoded
self.statusUser = decoded.status
print("UserData: name = \(userData.data.firstName) Lastname = \(userData.data.lastName) gender = \(userData.data.gender)")
} catch let error{
print("Error decode",error)
}
}
}
}
dataTask.resume()
}
I don't know how to throw all the data into the array
First of all iterating the indices is cumbersome, replace
for i in self.listId.indices {
print("This is id[\(i)] = \(listId[i])")
getUser(url: "\(listId[i])")
}
with
for id in self.listId {
print("This is \(id)")
getUser(url: id)
}
But this is not the right place to append the user data. Do it in getUser. But first declare the array as #Published (and in plural form)
#Published var users = [UserData]()
and delete the property userData because it's not needed.
#Published var userData = UserRespons(status: "??", data: UserData(id: "???", firstName: "???", lastName: "??", age: 4, gender: "???", country: "???"))
Now replace the decoding part in getUser with
let decoded = try JSONDecoder().decode(UserRespons.self, from: data)
self.statusUser = decoded.status
users.append(decoded.data)
print("UserData: name = \(decoded.data.firstName) Lastname = \(decoded.data.lastName) gender = \(decoded.data.gender)")
I recommend to move to async/await. It's much less code and it switches to the main thread automatically.
I'm getting duplicate results even after emptying my array first can someone please explain why is this happening I have tried to empty this array DriverOffers in so many different places but still getting the same result duplicate tableView cells everywhere.
func newOrders(){
//self.DriverOffers = [] here same result
let fireStore = Firestore.firestore()
let doc = fireStore.collection("الطلبات")
doc.addSnapshotListener { (query, err) in
if err != nil {
print(err?.localizedDescription ?? "")
}
//self.DriverOffers = []
query?.documentChanges.forEach({ (change) in
//self.DriverOffers = []
switch change.type{
case .added:
for document in query!.documents{
self.DriverOffers = []
let snap = document.data()
let name = snap["name"] as? String ?? ""
let phone = snap["phone"] as? String ?? ""
let time = snap["time"] as? String ?? ""
let marketName = snap["marketName"] as? String ?? "موقع محدد"
let price = snap["amount"] as? String ?? ""
DispatchQueue.main.async {
//self.DriverOffers = []
let offer = driverOrdersData(userUid: userid, name: name, phone: phone, amount: price)
self.DriverOffers.append(offer)
self.DriverOrdersTV.reloadData()
}
}
}
case .removed:
print("removed")
case .modified:
break
}
})
}
Use this method inside the tableViewCell and check.
override func prepareForReuse() {
super.prepareForReuse()
self.nameLabel.text = nil
}
in my project i'm try to implement a social app including a friends request using cloud firebase
I can't find a solution to avoid the fire base listener duplicate the record!
at the first opening, the list is correct and update, but if for any reason I add a friends the listener duplicate the record.
if I check my database Firestore the record is only one there is no double record.
if I exit and go back to the view all the record are ok.
here a picture of what happen when I add friends.
I attach my part of my code:
func testUpdatePendingUser(userLoggato: UserModel, utenteADDED: #escaping (UserModel)->(), vettoreUser: #escaping ([UserModel])->()) {
db.collection("user").document(userLoggato.userID).collection("pendingFriends")
.addSnapshotListener(includeMetadataChanges: false) { documentSnapshot, error in
var arrayUtentiStart = [UserModel]()
guard let snapshot = documentSnapshot else { return }
snapshot.documentChanges.forEach { (documentChange) in
switch documentChange.type {
case .added :
debugPrint("add")
let dict = documentChange.document.data()
let name = dict["name"] as? String ?? "na name"
let surname = dict["surname"] as? String ?? "na name"
let email = dict["email"] as? String ?? "na name"
let userLevel = dict["adminLevel"] as? String ?? "unable to get admin level"
let idUser = dict["userID"] as? String ?? "no ID"
let position1 = dict["position"] as? String ?? "na preferance position"
let position2 = dict["position2"] as? String ?? "na preferance position"
let vote = dict["vote"] as? Int ?? 0
self.downloadImageForAdmin(userID: idUser) { (urlImage) in
let utente = UserModel(name: name, surname: surname, email: email, userID: idUser, adminLevel: userLevel, immagine: urlImage, position: position1, position2: position2, vote: vote)
utenteADDED(utente)
arrayUtentiStart.append(utente)
vettoreUser(arrayUtentiStart)
}
case .modified :
debugPrint("mod")
case .removed :
debugPrint("rem")
}
}
}
}
this to be use on the contentView
func newPendinguser(userLoggato: UserModel){
usersPendingNEW = []
testUpdatePendingUser(userLoggato: userLoggato, utenteADDED: { (utenteCambiato) in
if !self.usersPendingNEW.isEmpty { // case is not empty , guess problem is here!!
self.usersPendingNEW.append(utenteCambiato)
}
}) { (vettoreIniziale) in
if self.usersPendingNEW.isEmpty{ // first view appear
self.usersPendingNEW = vettoreIniziale
}
}
}
i think this code will be duplicate your data.
utenteADDED(utente)
arrayUtentiStart.append(utente)
vettoreUser(arrayUtentiStart)
you design utenteADDED(utente) run when first time init, but when you have data in array, that code still run.
Check first run before run this code:
utenteADDED(utente)
Clear the array which is presenting the view and then assign the list received via listener
I have an app where a user reviews an array of users. Example:
["user1","user2","user3","user4"]
When the user reviews the first user the app should present the second user for voting. Unfortunately when a user votes on the user2, user3, user4; the value for user1 is what's put in the database. I tested the PHP via postman and there's no issue there, so it has to be in the swift code. Here is my code:
func loadCards()->[String] {
let username = user!["username"] as! String
let url = URL(string: "http://localhost/shotsCenter.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let body = "username=\(username)"
request.httpBody = body.data(using: .utf8)
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// getting main queue of proceeding inf to communicate back, in another way it will do it in background
// and user will no see changes :)
DispatchQueue.main.async(execute: {
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: AnyObject]
// clean up
self.valueArray.removeAll(keepingCapacity: false)
self.circleArray.removeAll(keepingCapacity: false)
// delcare new secure var to store json
guard let parseJSON = json else {
print("Error while parsing")
return
}
// declare new secure var to store $returnArray["users"]
guard let parseUSERS = parseJSON["users"] else {
print(parseJSON["message"])
return
}
self.valueArray = parseUSERS as! [AnyObject]
if self.valueArray.count > 0 {
let num_currentLoadedCardsArrayCap = (self.valueArray.count > MAX_BUFFER_SIZE) ? MAX_BUFFER_SIZE : self.valueArray.count
for (i, value) in self.valueArray.enumerated() {
let ava = self.valueArray[i]["ava"]
let id = self.valueArray[i]["id"]
let age = (NSString(format: "%#", self.valueArray[i]["age"] as! CVarArg))
let city = self.valueArray[i]["city"]
let state = self.valueArray[i]["state"]
self.age.append(age as AnyObject)
self.city.append(city as AnyObject)
self.state.append(state as AnyObject)
let url = NSURL(string: ava! as! String)!
let imageData = try? Data(contentsOf: url as URL)
let image = UIImage(data: imageData!)!
self.circleArray.append(image)
let reviewed = self.valueArray[i]["username"]
self.reviewed.append((reviewed as AnyObject) as! String)
print("reviewed user", reviewed! as Any)
let newCard = self.createDraggableViewWithData(at: i, value:value as! NSDictionary)
self.allCardsArray.append(newCard)
if i < num_currentLoadedCardsArrayCap {
self.currentLoadedCardsArray.append(newCard)
}
}
for (i,_) in self.currentLoadedCardsArray.enumerated() {
if i > 0 {
self.viewTinderBackGround.insertSubview(self.currentLoadedCardsArray[i], belowSubview: self.currentLoadedCardsArray[i - 1])
}
else {
self.viewTinderBackGround.addSubview(self.currentLoadedCardsArray[i])
}
self.currentIndex += 1
}
self.animateCardAfterSwiping()
self.perform(#selector(self.createDummyCard), with: nil, afterDelay: 1.0)
}
} catch {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = "\(error)"
})
return
}
} else {
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
})
return
}
})
} .resume()
return reviewed
}
func insertShot(_ rating: String, _ reviewed2: NSDictionary) {
let reviewer = user!["username"] as! String
let reviewed2 = reviewed[index]
let url = URL(string: "http://localhost/shotsCenter.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
// param to be passed to php file
let param = [
"user" : reviewer,
"revieweduser" : reviewed2,
"rating" : rating
] as [String : Any]
// body
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
// ... body
request.httpBody = createBodyWithParams(param as? [String : String], boundary: boundary)
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// get main queu to communicate back to user
DispatchQueue.main.async(execute: {
if error == nil {
do {
// json containes $returnArray from php
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// declare new var to store json inf
guard let parseJSON = json else {
print("Error while parsing")
return
}
// get message from $returnArray["message"]
let message = parseJSON["message"]
//print(message)
// if there is some message - post is made
if message != nil {
// reset UI
// self.msgTxt.text = ""
// switch to another scene
//self.tabBarController?.selectedIndex = 3
//_ = self.navigationController?.popViewController(animated: true)
}
} catch {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = "\(error)"
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
} else {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
})
}.resume()
}
No idea what currentIndex is for. Sth about the view hierarchy?
index is an interesting candidate. The only times it is used is in
let reviewed2 = reviewed[index]
and I see no modification to it. Since usernames are stored in there with
let reviewed = self.valueArray[i]["username"]
I think you are sending the same username for all four ratings, since reviewed2 is then made of the .httpBody. That's a guess from what I can see.
Tip: Codable
Codable may save you a lot of the ugliness of juggling around with AnyObject & co, since you'll have a [User] array instead of.. 4 separate arrays which you access with [index]. https://app.quicktype.io will get you started quickly by providing you with the parsing code for the given JSON.
In my viewController I have an array of class Users named arrayUtenti, with the code below I need to add that user to that array but for some reason that i cannot understand I get an empty array, what could happened?
**************EDITED WORKS**************
func vengodaEditPremuto(completionHandler:#escaping ([Users]) -> ()){ self.root.child("users").child("ZJq98gvOEHaLUqYTbp4xF52f2K23").observeSingleEvent(of: .value, with: { (snapshot) in
var TempArray: [Users] = []
guard
let value = snapshot.value as? [String:AnyObject],
let userid = value["id"],
let username = value["name"],
let useremail = value["email"]
else {return}
let utenteesistente = Users(id: userid as? String, name: username as? String, email: useremail as? String)
TempArray.append(utenteesistente)
self.arrayUtenti = TempArray
completionHandler(self.arrayUtenti)
print(self.arrayUtenti) //here is ok
})
print(self.arrayUtenti) //here the array is empty!!
}
Your code looks fine after my comment. This is the last thing you need to do to make a correct call:
vengodaEditPremuto(){ result in
print(result)
}