Strange behaviour on Firebase query result SWIFT - ios

I'm getting this error on the line let itemToAdd = snapshot.childSnapshot(forPath: "Shopa function that retrieves data from Firebase.
the output of the console in Could not cast value of type 'NSNull' (0x1118c8de0) to 'NSString' (0x10dda45d8)..
What I'm trying to do is to filter database ordering by one value
opening Timeand than get another value Shop Namefrom the returned entries in the snapshot.
here's the function:
func filterOpenShops(enterDoStuff: #escaping (Bool) -> ()) {
ref = Database.database().reference().child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Shops").child("Shops Opening Times")
let query = ref?.queryOrdered(byChild: "Opening Time").queryStarting(atValue: openingTimeQueryStart).queryEnding(atValue: openingTimeQueryEnd)
query?.observe(.value, with: { (snapshot) in
for childSnapshot in snapshot.children {
// new modification
if childSnapshot is DataSnapshot {
let itemToAdd = snapshot.childSnapshot(forPath: "Shop Name").value as! String // gets the open shop from snapshot
self.availableShopsArray.append(itemToAdd)
print(snapshot.children)
print(" Open Shops are \(self.availableShopsArray)")
}
}
// still asynchronous part
enterDoStuff(true)
// call next cascade function filterClosedShops only when data
})
// Sychronous part
print("opening query start is \(openingTimeQueryStart) and opening query end is \(openingTimeQueryEnd)")
} // end of filterOpenShops()
EDIT:
I rewrote the function as:
func filterOpenShops(enterDoStuff: #escaping (Bool) -> ()) {
// get from Firebase snapshot all shops opening times into an array of tuples
//shopOpeningTimeArray:[(storeName: String, weekdayNumber: String, opening1: Sring, closing1: String, opening2:String, closing2: String)]
ref = Database.database().reference().child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Shops").child("Shops Opening Times")
let query = ref?.queryOrdered(byChild: "Opening Time").queryStarting(atValue: String(describing: openingTimeQueryStart)).queryEnding(atValue: String(describing :openingTimeQueryEnd))
query?.observe(.value, with: { (snapshot) in // original is ok
// guard let data = snapshot.value as? [String:String] else { return }
for childSnapshot in snapshot.children {
print("snapshot is: \(childSnapshot)")
print("snapshot.childrend is: \(snapshot.children)")
guard let data = snapshot.value as? [String:String] else { return }
let itemToAdd = data["Shop Name"]
self.availableShopsArray.append(itemToAdd!)
print("Open Shop is: \(String(describing: itemToAdd))")
print(" Open Shops are \(self.availableShopsArray)")
}
// still asynchronous part
enterDoStuff(true)
// call next cascade function filterClosedShops only when data
print(" Open Shops are \(self.availableShopsArray)")
})
print("opening query start is \(openingTimeQueryStart) and opening query end is \(openingTimeQueryEnd)")
} // end of filterOpenShops()
but I still get a null object and not a [String:String] as expected.
The function that created the entries in Firebase is:
func postOpeningTime() {
// if shopNameTextfield.text != nil && openingTimeTextfield.text != nil && closingTimeTextfield.text != nil {
let shopName = shopNameTextfield.text!
let openingTime = openingTimeTextfield.text!
let closingTime = closingTimeTextfield.text!
// } else {return}
let post: [String:String] = [
"Shop Name" : shopName ,
"Opening Time" : openingTime ,
"Closing Time" : closingTime
]
var ref: DatabaseReference!
ref = Database.database().reference()
ref?.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Shops").child("Shops Opening Times").childByAutoId().setValue(post)
}
Now I have two behaviours:
1st: When querying for entries and finds values that are Int: completion get called but I get no snapshot print.
2nd: When querying for entries and find values that are String: completion doesn't get called but snapshot prints the right entries with values.
Can anyone please spot what's going on here?

I found the problem to bee the way I was casting query result.
Casting it as [String:String] produced to return because upshot was actually [String[String:String]] when all the values for entry's parameter were String, but as I changed Opening Time and Closing time to be Int, than I have to read the snapshot as [String[String:Any]].
So the final function is:
func filterOpenShops(setCompletion: #escaping (Bool) -> ()) {
// Empty the array for beginning of the search
self.availableShopsArray.removeAll()
var ref = Database.database().reference()
ref.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Shops").child("Shops Opening Times").queryOrdered(byChild: "Opening Time").queryStarting(atValue: openingTimeQueryStart).queryEnding(atValue: openingTimeQueryEnd).observe(.value) { (snapshot) in
print(snapshot)
if let data = snapshot.value as? [String : [String : Any]] {
for (_, value) in
data {
let shopName = value["Shop Name"] as! String
let active = value["Active"] as! String
if active == "true" {
self.availableShopsArray.append(shopName)
print("Shop_Name is :\(shopName)")
print("self.availableShopsArray is: \(self.availableShopsArray)")
}
}
} else {
print("No Shops")
}
// still asynchronous part
setCompletion(true)
// call next cascade function filterClosedShops only when data retrieving is finished
self.filterClosedShops(setCompletion: self.completionSetter)
print(" 1 Open Shops are \(self.availableShopsArray)")
}
} // end of filterOpenShops()

Related

Having Trouble Pulling Data From Firebase RT Database

Super new to coding so apologies if something is super obvious here.
I'm working on an app that I can use to keep track of my weight lifting split. I write the data like this:
public func writeNewExercise(splitName: String, day: Int, exerciseNum: Int, exerciseName: String, sets: String, repsSecs: String, isTimed: Bool, completion: #escaping (Bool) -> Void) {
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
exerciseRef = Database.database().reference(withPath: "\(user.uid)/splits/\(splitName)/day \(day)/exercise \(exerciseNum)")
var dataDictionary: [String: Any] = [:]
dataDictionary["Exercise Name"] = exerciseName
dataDictionary["Sets"] = sets
dataDictionary["Reps or Secs"] = repsSecs
dataDictionary["Is Timed"] = isTimed
exerciseRef.setValue(dataDictionary) { error, _ in
if error == nil {
completion(true)
return
} else {
completion(false)
return
}
}
}
This gives me a JSON dictionary in Firebase that looks like this:
{
"8aIzPgurRLPPEYDpXWv54r5JjvH3" : {
"splits" : {
"Test Split" : {
"day 1" : {
"exercise 0" : {
"Exercise Name" : "Curls",
"Is Timed" : false,
"Reps or Secs" : "12",
"Sets" : "4"
}
}
}
}
},
What I want to do now is to pull this data so I can insert each exercise into a tableView cell. Don't want to do anything fancy with it -- just be able to view it so I can follow my split. I'm doing this more for practice than practicality. I've tried pulling the data about 15 different ways, and no matter what I do it just won't work. I'm totally stumped. Here is the code I have right now:
public func downloadPost(splitName: String, day: Int, completion: #escaping (Bool) -> Void){
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
exerciseRef = Database.database().reference()
var exerciseArray = [Exercise]()
exerciseRef.child("Users").child(user.uid).child("splits").child(splitName).child("day \(day)").observe(.value) { snapshot in
if snapshot.exists(){
for x in 0...100{
let nameValue = snapshot.childSnapshot(forPath: "exercise \(x)/Exercise Name").value
let setsValue = snapshot.childSnapshot(forPath: "exercise \(x)/Sets").value
let repsOrSecsValue = snapshot.childSnapshot(forPath: "exercise \(x)//Sets/Reps or Secs").value
let isTimedValue = snapshot.childSnapshot(forPath: "exercise \(x)/Sets/Is Timed").value
let exercise = Exercise(name: "\(nameValue!)",
sets: "\(setsValue!)",
repsOrSecs: "\(repsOrSecsValue!)",
isTimed: isTimedValue as? Bool ?? false)
print(exercise.name)
print(exercise.sets)
print(exercise.repsOrSecs)
print(exercise.isTimed)
exerciseArray.append(exercise)
completion(true)
return
}
} else {
print("no snapshot exists")
}
print(exerciseArray)
}
}
Exercise is a custom class I've created that has a name, amount of sets, amount of reps, and a Bool "isTimed". This code prints:
no snapshot exists, []
Trying other things, I've got it to print something like:
null,
0,
0,
false
Some other stuff I've tried has been:
using slash navigation instead of chaining .childs in the .observe.value
using .getData instead of .observe
throwing DispatchQueue.main.async all over the place
making the exerciseRef be the whole database, then calling to the specific point when assigning the snapshot.value
Much else
I've probably put something like 15 hours into just this at this point, and I really cannot figure it out. Any help would be massively appreciated. I'll watch this post closely and post any info that I may have left out if it's needed.
Thanks!
UPDATE
Got everything working by using the code provided by Medo below. For others trying to do something like this, after pulling the array as Medo demonstrated, just set all the labels in your tableViewCell to ExportedArray[indexPath.row].theClassPropertyYouWant
Here is my solution:
public func downloadPost(splitName: String, day: Int, completion: #escaping (([Exercise]) -> ())){
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
exerciseRef = Database.database().reference()
var exerciseArray = [Exercise]()
exerciseRef.child(user.uid).child("splits").child(splitName).child("day \(day)").observe(.value, with: { snapshot in
guard let exercises = snapshot.children.allObjects as? [DataSnapshot] else {
print("Error: No snapshot")
return
}
for exercise in exercises {
let exerciseData = exercise.value as? [String:Any]
let exerciseName = exerciseData["Exercise Name"] as? String
let isTimed = exerciseData["Is Timed"] as? Bool
let repsOrSecs = exerciseData["Reps or Secs"] as? String
let sets = exerciseData["Sets"] as? String
let exerciseIndex = Exercise(name: "\(exerciseName)",
sets: "\(sets)",
repsOrSecs: "\(repsOrSecs)",
isTimed: isTimed)
exerciseArray.append(exerciseIndex)
}
completion(exerciseArray)
}
}
You can call the function downloadPost and extract the array from it like this:
downloadPost(splitName: "", day: 0, completion: {
aNewArray in
// aNewArray is your extracted array [Exercise]
print("\(aNewArray)")
})
Few things to be aware of:
If you want to ensure that your storing your exercises in order (and extract the data in order) then instead of having exercises 0, 1, 2... (in your database), name it by an id called "childByAutoId". Firebase will auto order them for you as you add/push or extract that data. Replace your writeNewExercise function with:
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
let key = Database.database().reference().childByAutoId().key ?? ""
exerciseRef = Database.database().reference(withPath: "\(user.uid)/splits/\(splitName)/day \(day)/\(key)")
var dataDictionary: [String: Any] = [:]
dataDictionary["Exercise Name"] = exerciseName
dataDictionary["Sets"] = sets
dataDictionary["Reps or Secs"] = repsSecs
dataDictionary["Is Timed"] = isTimed
exerciseRef.setValue(dataDictionary) { error, _ in
if error == nil {
completion(true)
return
} else {
completion(false)
return
}
}
Firebase Realtime Database is a breadth first search and download. So you should probably flatten out your database structure as much as possible. This means observing on exerciseRef.child("Users").child(user.uid).child("splits").child(splitName).child("day \(day)") would still download all the exercise days.

Swift Firebase Save/Update multiple parents with the same child values

I have an array of strings which is the "uid's" of users. I am trying to append data/children to these multiple "uid's". Adding children or updating children to individual parents/users is easy and I understand how to do it. The problem is that this array can either contain 1 uid or 50 uid's. Is it possible for me to take these uid's and then update them with the same value? I am unsure what code to provide since I am just trying everything to attack this.
With the code below, this is me send a message to other users.
Array of uid strings
var data = [String]()
Sample code of me sending a message to 2 users, just wanted to provide something here to show I know how to update/save data
private func sendMessageWithProperties(_ properties: [String: Any]) {
let businessRef = Database.database().reference().child("Business Group Chats Messages").child((group?.uid)!).child((Auth.auth().currentUser?.uid)!)
let ref = Database.database().reference().child("Business Group Chats Messages").child((Auth.auth().currentUser?.uid)!).child((group?.businessName)!)
let businesChildRef = businessRef.childByAutoId()
let childRef = ref.childByAutoId()
let fromID = Auth.auth().currentUser!.uid
let timeStamp = Int(Date().timeIntervalSince1970)
var value:[String: Any] = ["fromId" : fromID, "timeStamp" : timeStamp, "name": self.loggedInUserData?["name"] as? String]
properties.forEach { (k,v) in
value[k] = v
}
childRef.updateChildValues(value) { (err, ref) in
if err != nil {
print(err!)
return
}
Database.database().reference().child("Business Group Chats").child((self.group?.uid)!).child((Auth.auth().currentUser?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp, "businessName":(self.group?.businessName)!])
Database.database().reference().child("Business Group Chats").child((Auth.auth().currentUser?.uid)!).child((self.group?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp])
self.inputContainerView.inputTextField.text = nil
}
}
Here is me taking that array of "uid's" and then pulling and printing that I can access each "uid" through a array of strings. Allowing me to access, now I can append data to each.
Database.database().reference().child("Businesses").observe(.value, with: { snapshot in
if snapshot.exists() {
self.businessUID = snapshot.value as? NSDictionary
if let dict = snapshot.value as? NSDictionary {
for item in dict {
let json = JSON(item.value)
let businessUid = json["uid"].stringValue
for uid in self.data {
if uid == businessUid {
//print(uid)
self.businessessuids = uid
print(self.businessessuids)
Database.database().reference().child("Businesses").child(self.businessessuids).observe(.value, with: { snapshot in
print(snapshot)
print("Trying to pull data from multiple strings right here this shoudld work")
})
print("printing the values to match with the string UID's")
}
}
}
}
} else {
print("does not exist")
}
})

How can I get a list of children in a Firebase snapshot in order?

I'm working on an app that records when a user stops a scroll motion, appends the offset of the scroll and an elapsed time to a local array, and then uploads the scroll history to Firebase when the user closes the app.
The data in Firebase is stored with an auto ID at the top. Each scroll offset and elapsed time is then stored within its own auto ID child below the parent. In the Firebase web app, the children are in proper order.
I pull the data from Firebase like so:
ghostref.queryOrderedByKey().queryLimited(toLast: UInt(1)).observe(.childAdded, with: { (snapshot) in
guard let ghostdict = snapshot.value as? [String:[String:String]] else {
print("failure")
return
}
var downloadedghostarray = [(cameray:Float, timeelapse:Double)]()
for key in ghostdict.keys {
downloadedghostarray.append((cameray: Float(ghostdict[key]!["cameray"]!)!, timeelapse: Double(ghostdict[key]!["timeelapse"]!)!))
}
}
While I get the data I need, it is not in the proper order. Is there any way to pull Firebase children in the expected order? Maybe I can order the snapshot's children by key as well?
EDIT: Here is the data as it appears in the Firebase web app in the desired order:
And here is the array that renders using the code above:
By iterating the node fields by key and organizing them by key, you're effectively randomizing the elements in your list. Hash-based dictionaries/maps don't guarantee that order is maintained.
You're going to have to iterate the snapshot using children, which (I believe) ensures that order of the children is maintained. This order should allow you to push them into another array whose order is ensured.
class func downloadAllMessages(forUserID: String, completion: #escaping ([Message]) -> Swift.Void, locationCompletion: #escaping (String) -> Swift.Void) {
if let userID = Helper.shared.driverLoggedInDetails.detail?.userid {
let currentUserID = "D\(userID)"
dbReference.child("users").child(currentUserID).child("conversations_list").child(forUserID).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
let data = snapshot.value as! [String: Any]
let location = data["location"]!
locationCompletion(location as! String)
dbReference.child("messages").child(location as! String).observe(.childAdded, with: { (snap) in
if snap.exists() {
let receivedMessages = snap.value as! [String: Any]
var messages1 = [Message]()
let type = MessageType.text
let text = (receivedMessages as NSDictionary).object(forKey: "text") as? String
let mmText = (receivedMessages as NSDictionary).object(forKey: "mmText") as? String
let messageType = (receivedMessages as NSDictionary).object(forKey: "messageType") as? Int
let fromID = (receivedMessages as NSDictionary).object(forKey: "senderId")as? String
let timestamp = (receivedMessages as NSDictionary).object(forKey: "timeStamp")as? Int
let isRead = (receivedMessages as NSDictionary).object(forKey: "read")as? Bool
let isvisible = UserDefaults.standard.object(forKey: "chatwindow") as? Bool
if fromID != currentUserID, isvisible ?? false {
dbReference.child("messages").child(location as? String ?? "").child(snap.key).child("read").setValue(true)
}
if fromID == currentUserID {
let message = Message.init(type: type, textEn: text ?? "", textMM: mmText ?? "", owner: .receiver, timestamp: timestamp ?? 0, isRead: isRead ?? false, isSuggested: messageType == -1 ? true : false)
messages1.append(message)
} else {
let message = Message.init(type: type, textEn: text ?? "", textMM: mmText ?? "", owner: .sender, timestamp: timestamp ?? 0, isRead: isRead ?? false, isSuggested: messageType == -1 ? true : false)
messages1.append(message)
}
completion(messages1)
}else {
// LoadingIndicator.shared.hide()
completion([])
}
})
// LoadingIndicator.shared.hide()
completion([])
}
}
}
}
U can get by adding a number field in the firebase document from 1..n, so that you can use query based on ascending/ descending. The result will be your expected result

Swift 3 Firebase retrieving key and passing to view controller

I've spend hours looking at identical questions but none of the answers I've found are helping this issue. Simple app retrieves data from Firebase Database and passes to another view controller from the tableview. The main data will pass through but I can't edit the information without an identifying "key" which I tried to set as childByAutoID() but then changed to a timestamp. Regardless of the method, all I get is the entries info not the actual key itself.
func loadData() {
self.itemList.removeAll()
let ref = FIRDatabase.database().reference()
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
if let todoDict = snapshot.value as? [String:AnyObject] {
for (_,todoElement) in todoDict {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
print (snapshot.key);
}
}
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
If your data looks like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”
}
}
}
Then I would query like this:
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let child = child as? DataSnapshot
let key = child?.key as? String
if let todoElement = child?.value as? [String: Any] {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
self.tableView.reloadData()
}
}
})
Additionally, like I said in my comment you can just upload the key with the data if you’re using .updateChildValues(). Example:
let key = ref.child("userID!").childByAutoId().key
let feed = ["key": key,
“itemName”: itemName] as [String: Any]
let post = ["\(key)" : feed]
ref.child("userID").child("MyStuff").updateChildValues(post) // might want a completionBlock
Then you can get the key the same way you are getting the rest of the values. So your new data would look like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”,
key: “autoID”
}
}
}
The key you are trying to look for is located in the iterator of your for loop
Inside your if-let, try to do this:
for (key,todoElement) in todoDict {
print(key) // this is your childByAutoId key
}
This should solve the problem. Otherwise show us a screen of your database structure

Index out of range on Table Refresh?

I added refreshing capabilities for my tableView when the user pulls down on it. This works most of the time but occasionally I'll get a fatal error, Index out of range. The callback thats called to refresh the table does the following:
1) Empty's out data structures holding data to be displayed
2) Fetch data from Firebase database
3) Parse data into individual parts and insert into data structures
4) Refresh the table
However, in the cellForRowAt indexPath function of my tableView I'm getting the fatal error on the first line of the function:
let eventsOnDay = eventsForDate[allDates[indexPath.section]]!
Here's my code for the refresh callback:
var allDates = [DateStruct]() // Holds all unique dates of events
var eventsForDate = [DateStruct : [PetEvent]]() // Holds all events for each day
/// Read events from db, split into individual dates
///
func readEventsFromDb() {
// 1. Empty out data structures
eventsForDate.removeAll()
allDates.removeAll()
let dbRef = FIRDatabase.database().reference().child("pets").child(currentPet).child("events")
// 2. Fetch data from db
dbRef.observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
// 3. Split data into individual components
for child in snapshots{
if let data = child.value as? [String: Any] {
if let c = data["comment"] as? String, let p = data["user"] as? String, let t = data["type"] as? Int, let d = data["date"] as? UInt64 {
let event = PetEvent(comment: c, person: p, type: t, time: self.timeFromEpoch(time: Double(d)))
let eventDate = self.dateFromEpoch(time: Double(d))
if (self.eventsForDate[eventDate] != nil) {
self.eventsForDate[eventDate]!.append(event)
} else {
self.eventsForDate[eventDate] = [event]
}
}
}
}
self.allDates = Array(self.eventsForDate.keys).sorted {d1,d2 in
d2 < d1
}
// 4. Reload table
self.feedTable.reloadData()
self.refreshControl.endRefreshing()
}
})
}
I'm having a hard time figuring out why this is working most of the time but occasionally fails. Does anyone have an idea?
If your goal is to avoid the crash you can safely check for your eventOnDelay item by doing something like this:
if allDates.count > indexPath.section {
guard let eventsOnDay = eventsForDate[allDates[indexPath.section]] else {
//handle error here...
return
}
//Do whatever you need with eventsOnDay here
} else {
//Check your numberOfSections method, as it's not set up correctly
}
You also definitely need to make sure you're calling tableView.reloadData() and endRefreshing on the main thread as your Firebase callback likely comes back on a bg thread.
check wether the array(containing data to display) is not empty in your tableview's delegate and datasource method.
i.e. in cellForRow method
check if(yourArray.count > 0){
// Do your code
}
else{
// Dont
}
You can use different approach to update the table view content. I think there is delay getting data from firebase you are using callback dbRef.observeSingleEvent. you are removing the previous data at the beginning of the function call. you can try following two approaches to fix the issue
1. Remove empty data in dbRef.observeSingleEvent callback
var allDates = [DateStruct]() // Holds all unique dates of events
var eventsForDate = [DateStruct : [PetEvent]]() // Holds all events for each day
// Read events from db, split into individual dates
func readEventsFromDb() {
let dbRef = FIRDatabase.database().reference().child("pets").child(currentPet).child("events")
// 1. Fetch data from db
dbRef.observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
// 2. Empty out data structures
eventsForDate.removeAll()
allDates.removeAll()
// 3. Split data into individual components
for child in snapshots{
if let data = child.value as? [String: Any] {
if let c = data["comment"] as? String, let p = data["user"] as? String, let t = data["type"] as? Int, let d = data["date"] as? UInt64 {
let event = PetEvent(comment: c, person: p, type: t, time: self.timeFromEpoch(time: Double(d)))
let eventDate = self.dateFromEpoch(time: Double(d))
if (self.eventsForDate[eventDate] != nil) {
self.eventsForDate[eventDate]!.append(event)
} else {
self.eventsForDate[eventDate] = [event]
}
}
}
}
self.allDates = Array(self.eventsForDate.keys).sorted {d1,d2 in
d2 < d1
}
// 4. Check if this is on UI/Main thread. Reload table
self.feedTable.reloadData()
self.refreshControl.endRefreshing()
}
})
}
2. Use temporary arrays to hold the data and then update the main data with temporary array
//Read events from db, split into individual dates
func readEventsFromDb() {
let dbRef = FIRDatabase.database().reference().child("pets").child(currentPet).child("events")
// 1. Fetch data from db
dbRef.observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
var tempAllDates = [DateStruct]() // Holds all unique dates of events
var tempEventsForDate = [DateStruct : [PetEvent]]()
// 2. Split data into individual components
for child in snapshots{
if let data = child.value as? [String: Any] {
if let c = data["comment"] as? String, let p = data["user"] as? String, let t = data["type"] as? Int, let d = data["date"] as? UInt64 {
let event = PetEvent(comment: c, person: p, type: t, time: self.timeFromEpoch(time: Double(d)))
let eventDate = self.dateFromEpoch(time: Double(d))
if (self.tempEventsForDate[eventDate] != nil) {
self.tempEventsForDate[eventDate]!.append(event)
} else {
self. tempEventsForDate[eventDate] = [event]
}
}
}
}
self. tempAllDates = Array(self.eventsForDate.keys).sorted {d1,d2 in
d2 < d1
}
// 3. Empty out data structures
eventsForDate.removeAll()
allDates.removeAll()
// 4. Fill data with temporary array. check the size
if tempEventsForDate.count > 0{
eventsForDate = tempEventsForDate
}
if tempAllDates.count > 0{
allDates = tempAllDates
}
// 5. Check if this is on UI/Main thread. Reload table
self.feedTable.reloadData()
self.refreshControl.endRefreshing()
}
})
}
Hope this approaches will help to solve the issue :)

Resources