I have a collection that stores posts. Each post has some strings and references to images. I first call getDocuments and within that I call getData to get the corresponding image to a document from the stored reference. At the end I want to append an Object that contains the strings and the UiImages of this post. However, the Object I append contains the strings but the images are empty since it it does not wait on the firebase getData method. How can I make this work, I am fairly new to swift and firebase.
Here is some example code:
query.getDocuments { snapshot, error in
if error == nil{
if let snapshot = snapshot {
for doc in snapshot.documents{
}
var imageUrls = [String]()
imageUrls.append(contentsOf: [doc["imageOne"] as? String ?? "", doc["imageTwo"] as? String ?? "", doc["imageThree"] as? String ?? "", doc["imageFour"] as? String ?? "", doc["imageFive"] as? String ?? ""])
var images = [UIImage]()
for path in imageUrls{
let storageRef = Storage.storage().reference()
let fileRef = storageRef.child(path)
fileRef.getData(maxSize: 5 * 1024 * 1024) { data, error in
if error == nil && data != nil{
if let image = UIImage(data: data!){
images.append(image)
}
}
else{
//handle error
}
}
}
//I want to wait here till the getData method is finished so I dont get the error
// "index error" because images[] is empty
loadedPost.append(LoadedPost(id: doc.documentID, Userid: doc["userID"] as? String ?? "", timestamp: doc["timestamp"] as? String ?? "", imageOne: images[0], imageTwo: images[1], imageThree: images[2], imageFour: images[3], imageFive: images[4]))
}
}
}
CodePudding user response:
The reason it's not being included is because you're appending the image asynchronously inside of the closure. It should look like this.
fileRef.getData(maxSize: 5 * 1024 * 1024) { data, error in
if error == nil && data != nil{
if let image = UIImage(data: data!){
images.append(image)
//APPEND ENTIRE THING HERE
}
}
else{
//handle error
}
}
}
To make things a bit clearer, here's your. "Flow Of Data" as I call it.
getDocuments(..) -> Async (Everything below here is contained in this call.)
looping over docs (Nothing inside brackets?) -> Sync
appending urls -> Sync
looping over urls -> Sync
getData(...) -> Async
appending to loadedPost -> Sync
Looking at it like that if you have any sync stuff happening AFTER an Async call, then you will run it immediately. So your code could run like this. Why? Because Async calls (Mostly Closures) can happen at any time, and their callback is not guaranteed.
getDocuments(...)
loop over docs
append urls
loop over urls
append to loadedPost
//Some other stuff could happen elsewhere.
//Even more stuff might happen.
getData(..)
Returning
This is a way that you can return from an async call after it's finished. You can also pass values in your completion if you want data to be returned as well.
func someFunc(string: String, completion: @escaping () -> void {
someClass.someAsyncCall(..) { data, error in
if error == nil {
completion()
}
}
}
class someClass {
func someOtherFunc() {
//Calling our custom completion used above.
yourObject.someFunc(someString) { _ in
//Function async was finished.
}
}
}
CodePudding user response:
Try this out:
query.getDocuments { snapshot, error in
if error == nil{
if let snapshot = snapshot {
for doc in snapshot.documents{
}
var imageUrls = [String]()
imageUrls.append(contentsOf: [doc["imageOne"] as? String ?? "", doc["imageTwo"] as? String ?? "", doc["imageThree"] as? String ?? "", doc["imageFour"] as? String ?? "", doc["imageFive"] as? String ?? ""])
var images = [UIImage]()
let group = DispatchGroup()
for path in imageUrls{
group.enter()
let storageRef = Storage.storage().reference()
let fileRef = storageRef.child(path)
fileRef.getData(maxSize: 5 * 1024 * 1024) { data, error in
if error == nil && data != nil{
if let image = UIImage(data: data!){
images.append(image)
group.leave()
} else {
group.leave()
}
}
else{
group.leave()()
//handle error
}
}
}
group.notify(queue: .main) {
loadedPost.append(LoadedPost(id: doc.documentID, Userid: doc["userID"] as? String ?? "", timestamp: doc["timestamp"] as? String ?? "", imageOne: images[0], imageTwo: images[1], imageThree: images[2], imageFour: images[3], imageFive: images[4]))
}
}
}
}