Reload TableView data only after downloading everything from the firebase database Swift - swift

I have been searching for a while now, but I don't know how to deal with this problem.
I have a Firebase Database with some data and the links to images stored into the Storage.
I want my UICollectionView to be reloaded after I downloaded every single image from the database, and not only the first one.
newRef.observeSingleEvent(of: .value, with: { (snapshotOne) in
for child in snapshotOne.children {
let snap = child as! DataSnapshot
let key = snap.key
newRef.child(key).observeSingleEvent(of: .value, with: { (snapshotTwo) in
for child in snapshotTwo.children {
let snappotto = child as! DataSnapshot
var imageDownloaded: UIImage?
if let dictionary = snappotto.value as? [String : AnyObject] {
let url = URL(string: dictionary["imageURL"] as! String)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("[Error]: \(String(describing: error))")
return
}
imageDownloaded = UIImage(data: data!)
let person = Person(name: dictionary["name"] as! String, surname: dictionary["surname"] as! String, tags: [.castana], image: imageDownloaded!)
self.storedData.append(person)
self.filteredData = self.storedData
DispatchQueue.main.sync {
UIApplication.shared.endIgnoringInteractionEvents()
self.collectionView.reloadData()
self.dismiss(animated: false, completion: nil)
}
}.resume()
}
}
}) { (error) in
print("[Error]: \(String(describing: error))")
}
}
}) { (error) in
print("[Error]: \(error)")
}
This is the code I use, but this updates my UICollectionView after only the first image is downloaded, and with that it endsIgnoringInteractionEvents - and allows the user to reload the data again and again and this causes a lot of images to duplicate.
How can I move the reloading and the endIgnoringInteractionEvents after every single item from my database is downloaded?
The database is structured like this:
| folder
-| user
--| autoId
---| name
---| surname
---| imageUrl
--|autoId
---| name
---| surname
---| imageUrl
-| user
--| autoId
---| name
---| surname
---| imageUrl
Thanks a lot, NicopDev

You're nesting observers on nested data, which seems like a waste of code. When you attach an observer to a location, all data under that location is loaded already.
So you can just loop over the nested snapshot to get the same result:
newRef.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let userSnapshot = child as! DataSnapshot
let userKey = userSnapshot.key
for child in userSnapshot.children {
let imageSnapshot = child as! DataSnapshot
var imageDownloaded: UIImage?
...
With that out of the way, let's move on to your real question: how can you detect when all images have loaded.
One simple, cross-platform way to do this is by simply counting how many images you have loaded, and compare that to how many images you know exist. Since you have a tree of only existing images, you can do both in a iteration over the double nested structure
let knownImageCount = 0 // we start with no knowledge of any image
let loadedImageCount = 0 // we also haven't loaded any image yet
for child in snapshot.children {
let userSnapshot = child as! DataSnapshot
let userKey = userSnapshot.key
knownImageCount = knownImageCount + userSnapshot.childrenCount // we've found out about N more images
for child in userSnapshot.children {
let imageSnapshot = child as! DataSnapshot
var imageDownloaded: UIImage?
...
URLSession.shared.dataTask(with: url!) { (data, response, error) in
...
loadedImageCount = loadedImageCount + 1 // we loaded an additional image
if loadedImageCount == knownImageCount {
... // we've loaded all known images, we're done!
}
}.resume()

Related

Firebase: cannot retrieve children if they are less than the specified number in queryLimited

I have the following function that gets invoked when a user scrolls conversation tableview, it works well but if the remaining children are less than the specified number it retrieves none! What is more interesting is that those values appear in print(snapshot.value) but not in print(child). How can I get all nodes even if they are less than the specified number?
Thank you.
func fetchBatch(betweenUser userID: String, fromTimestamp: Double , completion: #escaping([Message]) -> ()){
guard let currentUID = Auth.auth().currentUser?.uid else { return }
var messages = [Message]()
REF_MESSAGES.child(currentUID).child(userID).queryOrdered(byChild: "timestamp").queryEnding(atValue: fromTimestamp).queryLimited(toLast: 20).observeSingleEvent(of: .value) { snapshot in
print(snapshot.value)
for child in snapshot.children {
print(child)
if let childSnapshot = child as? DataSnapshot {
guard let dictionary = childSnapshot.value as? [String: AnyObject] else {return}
let message = Message(dictionary: dictionary)
messages.append(message)
}
}
return completion(messages)
}
}
Just a guess but you may have a problem with your data structure.
That guard statement
guard let dictionary = childSnapshot.value as? [String: AnyObject] else {return}
will prevent the messages array from being fully populated when it fails.
My guess is your retrieving say, 10 child notes, then as the code iterates over them, at some point the guard statement fails due to the structure. For example a [Int: Any] instead of a [String: Any]
The end result is that not all of the child nodes are not added to the array - which in turn means there's less elements in the array than was was actually retrieved from Firebase.

My images are not loading in order in my table view

I have an iOS app that has an events section where users can post events and attach a flyer image. everything works except two mages show up on top no matter what I do. And I have programmed the app to show the most current post at the top of the table view. Any help would be greatly appreciated.
eventsDatabaseHandle = eventsRef?.child("Church Events").observe(.childAdded, with: { (snaphot) in
let eventPost = snaphot.value as! [String: Any]
var event = Event(title: eventPost["eventtitle"] as! String,
timestamp: eventPost["eventdate"] as! String,
location: eventPost["eventlocation"] as! String,
image: nil)
let task = URLSession.shared.dataTask(with: URL(string: eventPost["ImageUrl"] as! String)!) { data, _, error in
if let image: UIImage = UIImage(data: data!) {
event.image = image
DispatchQueue.main.async {
self.events.append(event)
self.tableView.reloadData()
}
}
}
task.resume()
})
Have you tried this method?
event.image = UIImage(data: image)
If you tried this method, there may be an error :
eventPost["ImageUrl"]
Check with the name in the database.

Dispatch Queue Async Call

I am firing off a network request inside a for loop that is within another network request. I'm using Core Data but I am fairly certain this is not a Core Data issue, and is an async issue.
The 2 print statements inside the the Firebase request print the data properly, but without the DispatchQueue the array returns as empty (before the network request completes).
Here's a picture of the crash:
Here is the code itself:
var userReps = [UserRepresentation]()
// Fetch all Friends -> update Core Data accordingly
func fetchFriendsFromServer(completion: #escaping (Error?) -> Void = { _ in}){
let backgroundContext = CoreDataStack.shared.container.newBackgroundContext()
// 1. Fetch all friends from Firebase
FirebaseDatabase.UserDatabaseReference.child(CoreUserController.shared.userPhoneNumber).child(UserKeys.UserFriends).child(UserKeys.UserAcceptedFriends).observe(.value) { (data) in
if let dictionary = data.value as? [String: String] {
var userReps = [UserRepresentation]()
let group = DispatchGroup()
group.enter()
for friend in dictionary {
let friendName = friend.value
let friendId = friend.key
FirebaseDatabase.UserDatabaseReference.child(friendId).observe(.value, with: { (data) in
if let dictionary = data.value as? [String: Any] {
guard let gender = dictionary[UserKeys.UserGender] as? String else {return}
guard let bio = dictionary[UserKeys.UserBio] as? String else {return}
guard let status = dictionary[UserKeys.UserStatus] as? String else {return}
guard let avatarUrl = dictionary[UserKeys.UserAvatarUrlKey] as? String else {return}
let friendRepresentation = UserRepresentation(avatarUrl: avatarUrl, name: friendName, number: friendId, gender: gender, status: status, bio: bio)
userReps.append(friendRepresentation)
print("HERE, friends fetched: ", friendRepresentation)
print("HERE, reps fetched: ", userReps)
group.leave()
}
})
}
group.notify(queue: .main) {
// 2. Update Core Data value with Firebase values
self.updateFriends(with: userReps, in: backgroundContext)
// 3. Save Core Data background context
do {
try CoreDataStack.shared.save(context: backgroundContext)
} catch let error {
print("HERE. Error saving changes to core data: \(error.localizedDescription)")
}
}
}
}
}
Any help would go a long way
Since
let group = DispatchGroup()
is a local variable and you use observe here
FirebaseDatabase.UserDatabaseReference.child(friendId).observe(.value, with: { (data) in
it will re-call it after function deallocation either make it an instance variable or make this single observe
FirebaseDatabase.UserDatabaseReference.child(friendId).observeSingleEvent(of:.value) { (data) in
Also make enter inside the for loop
for friend in dictionary {
group.enter()

Retrieving multiple firebase nodes swift

Im retrieving data from a firebase backend which is below. My JSON structure has 2 child notes from my understanding With the code in my view did load, i can access the users node and i can print the "email" & "provider" -
However, my main goal is to actually access the 'planits' node and get the "images" & "planit" details. I am just pretty stuck on a way to implement that. I would appreciate any and all of the help provided. Thank you!
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
refHandle = ref.observe(DataEventType.value, with: { (snapshot) in
let dataDict = snapshot.value as! [String: AnyObject]
print(dataDict)
})
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
//Get user value
if snapshot.exists() == true {
for child in snapshot.children {
let value = snapshot.value as? NSDictionary
let EmailString = value!["email"] as! String
self.userEmail.append(EmailString)
print(self.userEmail)
// BELOW IS MY FAILED ATTEMPT AT ACCESSING THE 'planits' node - Nothing Prints
let image = value!["images"] as! String
print(image)
}
}
}) { (error) in
print(error.localizedDescription)
}

Swift loading images from firebase

Does someone know why this is not working? The tableView is empty and not showing anything even though there are items in the database and storage. This worked fine before I implemented the loading of the images from storage which you will see at the bottom of this code that I have pasted in. The food.append() statement used to be outside the storageRef.getData() closure (since it didn't exist) however if I take it out now it won't be able to access recipeImage since it's declared within the closure. Is it not working because it's in the closure? If so how do I fix it?
let parentRef = Database.database().reference().child("Recipes")
let storage = Storage.storage()
parentRef.observe(.value, with: { snapshot in
//Processes values received from server
if ( snapshot.value is NSNull ) {
// DATA WAS NOT FOUND
print("– – – Data was not found – – –")
} else {
//Clears array so that it does not load duplicates
food = []
// DATA WAS FOUND
for user_child in (snapshot.children) {
let user_snap = user_child as! DataSnapshot
let dict = user_snap.value as! [String: String?]
//Defines variables for labels
let recipeName = dict["Name"] as? String
let recipeDescription = dict["Description"] as? String
let downloadURL = dict["Image"] as? String
let storageRef = storage.reference(forURL: downloadURL!)
storageRef.getData(maxSize: 1 * 1024 * 1024) { (data, error) -> Void in
let recipeImage = UIImage(data: data!)
food.append(Element(name: recipeName!, description: recipeDescription!, image: recipeImage!))
}
}
self.tableView.reloadData()
}
})
Move
self.tableView.reloadData()
after
food.append(Element(name: recipeName!, description: recipeDescription!, image: recipeImage!))