so I'm trying to get data from firestore using this functionL
static func getData() -> [Post]{
let db = Firestore.firestore()
var posts = [Post]()
db.collection("Posts").getDocuments { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error retreiving snapshots \(error!)")
return
}
for document in snapshot.documents{
posts.append(Post(t: document.data()["title"] as? String ?? "", a: document.data()["author"] as? String ?? "", pm: document.data()["priceMax"] as? Double ?? 0.0, c: document.data()["content"] as? String ?? ""))
print("New Post... ")
print(document.data())
print(posts.count)
print(posts[0].title)
}
return
}
print("test")
return posts
}
and from the print statements I can tell that it gets the data, but the function never ends. print("test") never runs, and thus the posts are never returned. How can I change this so that it returns the data?
Getting data from Firestore is an asynchronous call. Try this out:
func getData(completion: #escaping([Post], Error?) -> Void) {
let db = Firestore.firestore()
var posts = [Post]()
db.collection("Posts").getDocuments { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error retreiving snapshots \(error!)")
completion(posts, error)
}
for document in snapshot.documents{
posts.append(Post(t: document.data()["title"] as? String ?? "", a: document.data()["author"] as? String ?? "", pm: document.data()["priceMax"] as? Double ?? 0.0, c: document.data()["content"] as? String ?? ""))
print("New Post... ")
print(document.data())
print(posts.count)
print(posts[0].title)
}
completion(posts, error)
}
}
Then call the function like this:
getData { posts, error in
print("Posts: \(posts)")
}
Related
I'm trying to fetch subcollection of my users document with code below
func readData(){
let userId = Auth.auth().currentUser?.uid
self.db.collection("users/\(userId)/saved").getDocuments { (snapshot, err) in
if let err = err {
print("err")
}
if let userId != nil {
for document in snapshot!.documents {
let docId = document.documentID
let cty = document.get("city") as! String
let ccode = document.get("code") as! String
let countr = document.get("country") as! String
print(cty, ccode, countr,docId)
}
}
}
but my code doesn't print anything, I don't understand the problem, documents exsist, see picture below
You're using illegal syntax with the userId check in the snapshot return but the logic flow is the bigger problem. I would recommend you check if the user is signed in before grabbing the subcollection and checking if there is a viable snapshot instead of checking the state of authentication.
func readData() {
guard let userId = Auth.auth().currentUser?.uid else {
return
}
db.collection("users/\(userId)/saved").getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
if let error = error {
print(error)
}
return
}
for doc in snapshot.documents {
guard let city = doc.get("city") as? String,
let code = doc.get("code") as? String,
let country = doc.get("country") as? String else {
continue // continue document loop
}
let docId = doc.documentID
print(city, code, country, docId)
}
}
}
I've got a little problem gaining objects from data base.It contains 4 objects(4 images specifically), they're coming but for some reason they overlap. If I print out query item, the logo shows 4 references to the images which is completely correct but when start setting data up to the image, instead of getting 4 images, I get 16. Is there any way to fix? I though it could be fixed by changing the model from struct to class to avoid duplicating but it didn't work out. Maybe a triple loop causes the problem.
class APIManager {
private init() {}
static let shared = APIManager()
func fetchData(collectionType: CollectionType, completion: #escaping (Document) -> Void) {
let dataBase = self.configureFireBase()
dataBase.collection(collectionType.rawValue).getDocuments { (querySnapshot, error) in
if let error = error {
print("Error received trying to get data: \(error)")
}else{
guard let query = querySnapshot?.documents else {return}
for queryItem in query {
for(name, price) in queryItem.data() {
if name == "field1" {
self.getImage { (sneakerImage) in
let documentToAppend = Document(name: queryItem.documentID, field1: price as? String ?? "", field2: queryItem.documentID, image: sneakerImage)
completion(documentToAppend)
}
}
}
}
}
}
}
private func getImage(imageHandler: #escaping (UIImage)->Void) {
let storage = Storage.storage()
let reference = storage.reference(forURL: "gs://sneakershop-b309a.appspot.com").child("pictures")
reference.listAll { (result, error) in
for item in result.items {
print(item)
item.getData(maxSize: 1024 * 1024) { (data, error) in
if let error = error {
print("error: \(error.localizedDescription)")
}else{
guard let image = UIImage(data: data ?? Data()) else {return}
imageHandler(image)
}
}
}
}
}
private func configureFireBase() -> Firestore {
var db: Firestore!
let settings = FirestoreSettings()
Firestore.firestore().settings = settings
db = Firestore.firestore()
return db
}
}
How can I get the value from firstName from the inside:
func saveImage(name: String, postURL:URL, completion: #escaping ((_ url: URL?) -> ())){
//Get sspecific document from current user
let docRef = Firestore.firestore().collection("users").whereField("uid", isEqualTo: Auth.auth().currentUser?.uid ?? "")
var firstName = ""
// Get data
docRef.getDocuments { (querySnapshot, err) in
var firstName = ""
if let err = err {
print("ERROR: ")
print(err.localizedDescription)
return
} else if querySnapshot!.documents.count != 1 {
print("More than one documents or none")
} else {
let document = querySnapshot!.documents.first
let dataDescription = document?.data()
firstName = dataDescription?["firstname"] as! String
}
}
// This uploads the data
let dict = ["title": postDescriptionTitle.text!,
"description": postDescription.text!,
"Address": addressField.text!,
"Zipcode": zipcodeField.text!,
"timestamp": [".sv":"timestamp"],
"Author":firstName,
"postUrl": postURL.absoluteString]
as [String: Any]
self.ref.child("post").childByAutoId().setValue(dict)
}
It looks like it's out of scope, how can I store it or access it without storing it in another variable?
As you can see, I'm trying to upload the variable firstName to the database. So in this part:
"Author":firstName,
I should be getting the value so I can give it to Author
Just move the "upload data" part inside the completion block like this:
func saveImage(name: String, postURL:URL, completion: #escaping ((_ url: URL?) -> ())) {
//Get sspecific document from current user
let docRef = Firestore.firestore().collection("users").whereField("uid", isEqualTo: Auth.auth().currentUser?.uid ?? "")
// Get data
docRef.getDocuments { (querySnapshot, err) in
if let err = err {
print("ERROR: ")
print(err.localizedDescription)
return
} else if querySnapshot!.documents.count != 1 {
print("More than one documents or none")
} else {
let document = querySnapshot!.documents.first
let dataDescription = document?.data()
let firstName = dataDescription?["firstname"] as! String
// This uploads the data
let dict = ["title": self.postDescriptionTitle.text!,
"description": self.postDescription.text!,
"Address": self.addressField.text!,
"Zipcode": self.zipcodeField.text!,
"timestamp": [".sv":"timestamp"],
"Author": firstName,
"postUrl": postURL.absoluteString] as [String: Any]
self.ref.child("post").childByAutoId().setValue(dict)
}
}
}
Also for what are you using the completion argument in your saveImage function?
I am trying to fetch data from Firebase Firestore. I am successful in fetching the data, however, I am displaying it in a UITableView and I need it to fetch it in real-time. I am using the getDocuments method and I cannot add a snapshot listener because I cannot write over the getDocuments arguments.
func loadPartiesDataFromFirebase() {
let db = Firestore.firestore()
db.collection("parties").getDocuments() { snapshot, err in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in (snapshot!.documents) {
let title = document.data()["title"] as? String ?? "New Party"
let location = document.data()["location"] as? String ?? "No Location"
let date = document.data()["date"] as? String ?? "No Date"
let startTime = document.data()["startTime"] as? String ?? "No Start Time"
let endTime = document.data()["endTime"] as? String ?? "No End Time"
self.parties.append(Party(title: title, location: location, date: date, startTime: startTime, endTime: endTime))
}
}
self.yourPartiesTableView.reloadData()
}
I wanted it to continuously display the data from Firestore in real-time. How can I do that in Swift?
You need to add a listener to the collection.
let db = Firestore.firestore()
let listener = db.collection("parties").addSnapshotListener { (snapshot, error) in
switch (snapshot, error) {
case (.none, .none):
print("no data")
case (.none, .some(let error)):
print("some error \(error.localizedDescription)")
case (.some(let snapshot), _):
print("collection updated, now it contains \(snapshot.documents.count) documents")
}
}
Also, you can store a reference to your listener and the remove it when you don't need it.
listener.remove()
This question already has answers here:
Wait until swift for loop with asynchronous network requests finishes executing
(10 answers)
Closed 4 years ago.
I would like to use asynchronous tasking for my app with using DispatchQueue.global().async and DispatchQueue.main.async, but it doesn't work.
I would like to get the data from firebase and then make List and pass it to closure. But in the code below, the timing completion called is first and then posts.append is called.
func retrieveData(completion: #escaping ([Post]) -> Void) {
var posts: [Post] = []
let postsColRef = db.collection("posts").order(by: "createdAt").limit(to: 3)
postsColRef.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Document data: \(error)")
} else {
DispatchQueue.global().async {
for document in querySnapshot!.documents {
let data = document.data()
let userId = data["userId"] as? String
let postImage = data["postImageURL"] as? String
let createdAt = data["createdAt"] as? String
let docRef = db.collection("users").document(userId!)
docRef.getDocument() { (document, error) in
if let document = document, document.exists {
let data = document.data()!
let userName = data["userName"] as? String
let post = Post(
userId: userId!,
userName: userName!,
postImageURL: postImage!,
createdAt: createdAt!
)
print("When append called")
posts.append(post)
}
}
}
DispatchQueue.main.async {
print("When completion called")
print(posts)
completion(posts)
}
}
}
}
}
I would like to complete for loop at first, and then go to completion. Could anybody give me any idea?
I just found this question(Wait until swift for loop with asynchronous network requests finishes executing) and tried the code below and it worked. I'm sorry for everybody who checked this question. From next time, at first I'm going to search the existing questions. Thank you.
func retrieveData(completion: #escaping ([Post]) -> Void) {
var posts: [Post] = []
let postsColRef = db.collection("posts").order(by: "createdAt").limit(to: 3)
let group = DispatchGroup()
postsColRef.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Document data: \(error)")
} else {
for document in querySnapshot!.documents {
group.enter()
let data = document.data()
let userId = data["userId"] as? String
let postImage = data["postImageURL"] as? String
let createdAt = data["createdAt"] as? String
//投稿に紐づくユーザーデータを取得して合わせてpostArrayに挿入
let docRef = db.collection("users").document(userId!)
docRef.getDocument() { (document, error) in
if let document = document, document.exists {
let data = document.data()!
let userName = data["userName"] as? String
let post = Post(
userId: userId!,
userName: userName!,
postImageURL: postImage!,
createdAt: createdAt!
)
posts.append(post)
group.leave()
}
}
}
group.notify(queue: .main) {
print(posts)
completion(posts)
}
}
}
}