How to make UIRefreshControl end refreshing when finish loading from Firebase - ios

The following method is my refreshing data method. It will read from the firebase to get the newest data. Because firebase is unsynchronized so I wait 5 seconds before endRefreshing to wait for Firebase to finish the reading process.
func refreshData() {
//Remove old data
self.items.removeAll()
//Renew all data
var ref: DatabaseReference!
ref = Database.database().reference(withPath: "tasks")
//Loading local drafts
var drafts : [Task]!
if let local_drafts = NSKeyedUnarchiver.unarchiveObject(withFile: Task.ArchiveURL.path) as? [Task] {
drafts = local_drafts
}
else{
drafts = []
}
//Reloading the database
ref.observe(.value, with: { snapshot in
var newItems: [Task] = []
self.num_of_tasks = Int(snapshot.childrenCount)
for item in snapshot.children {
//let local = item as! DataSnapshot
//let snapshotValue = local.value as! [String: AnyObject]
//print(snapshotValue["main_content"] as! String!)
let taskItem = Task(snapshot: item as! DataSnapshot)
newItems.append(taskItem!)
}
let merged = drafts + newItems
self.items = merged
self.tableView.reloadData()
//Wait for 5 seconds
let deadlineTime = DispatchTime.now() + .seconds(3)
DispatchQueue.main.asyncAfter(deadline: deadlineTime, execute: {
self.refreshControl!.endRefreshing()
})
})
}
But this is not a good solution because sometimes all data is loaded but the refreshing is still continuing, which makes it strange. Is there a way to solve this kind of situation? Make the refreshControl end refreshing until I load all data from firebase?

Beneath is one way to do it. It will wait until the async data has finished downloaded, and than when the completionHandler return either true or false, the refreshControl will stop refresh. I do not had a compiler here so I hope it works without errors.
func viewDidLoad(){
refreshData{ _ in
self.refreshControl!.endRefreshing()
}
}
func refreshData(completionHandler:#escaping (Bool)->() ) {
//Remove old data
self.items.removeAll()
//Renew all data
var ref: DatabaseReference!
ref = Database.database().reference(withPath: "tasks")
//Loading local drafts
var drafts : [Task]!
if let local_drafts = NSKeyedUnarchiver.unarchiveObject(withFile: Task.ArchiveURL.path) as? [Task] {
drafts = local_drafts
}
else{
drafts = []
}
//Reloading the database
ref.observe(.value, with: { snapshot in
var newItems: [Task] = []
self.num_of_tasks = Int(snapshot.childrenCount)
for item in snapshot.children {
//let local = item as! DataSnapshot
//let snapshotValue = local.value as! [String: AnyObject]
//print(snapshotValue["main_content"] as! String!)
let taskItem = Task(snapshot: item as! DataSnapshot)
newItems.append(taskItem!)
}
let merged = drafts + newItems
self.items = merged
self.tableView.reloadData()
completionHandler(true)
})
}

Related

Swift only stores last item in Array

I want to retrieve some data using firebase and store it in an empty array. Afterwards I have to store it in a global array (yes I know I shouldn't do that).
But the array only stores the last variable. I can assume that it is because the way firebase is retrieving the data in this function.
But i would like to have all the values appended to the Array.
Maybe someone can help me :)
static func jobs(for user: User, completion: #escaping ([TeamMember]) -> Void) {
let ref = Database.database().reference().child("team").child(user.uid)
ref.observe(DataEventType.value, with: { (snapshot) in
for case let child as DataSnapshot in snapshot.children {
guard let dict = child.value as? [String: Any] else {
print("Error")
return completion([])
}
let memberJob = dict["memberJob"] as! String
print("memberJob: \(memberJob)")
var memberJobs: [String] = []
memberJobs.append(memberJob)
for job in memberJobs {
print("New Job incoming: ", job)
}
globalJobs = memberJobs
}
})
}
You create a new array every loop with var memberJobs: [String] = []
var memberJobs: [String] = []
memberJobs.append(memberJob)
Move this line out
var memberJobs: [String] = []
static func jobs(for user: User, completion: #escaping ([TeamMember]) -> Void) {
let ref = Database.database().reference().child("team").child(user.uid)
var memberJobs: [String] = [] /// hererererre
ref.observe(DataEventType.value, with: { (snapshot) in
for case let child as DataSnapshot in snapshot.children {
guard let dict = child.value as? [String: Any] else {
print("Error")
return completion([])
}
let memberJob = dict["memberJob"] as! String
print("memberJob: \(memberJob)")
memberJobs.append(memberJob)
for job in memberJobs {
print("New Job incoming: ", job)
}
}
globalJobs = memberJobs
})
}
You are creating a new empty array in each iteration of the loop.
Create it once before the loop and assign the array to globalJobs after the loop
static func jobs(for user: User, completion: #escaping ([TeamMember]) -> Void) {
let ref = Database.database().reference().child("team").child(user.uid)
ref.observe(DataEventType.value, with: { (snapshot) in
var memberJobs: [String] = []
for case let child as DataSnapshot in snapshot.children {
guard let dict = child.value as? [String: Any] else {
print("Error")
return completion([])
}
let memberJob = dict["memberJob"] as! String
print("memberJob: \(memberJob)")
memberJobs.append(memberJob)
}
for job in memberJobs {
print("New Job incoming: ", job)
}
globalJobs = memberJobs
})
}
And the completion handler makes no sense if you call it only on failure.
This line is looping over each child in the snapshot
for case let child as DataSnapshot in snapshot.children {
inside that loop you create var memberJobs: [String] = [] which is an empty array. Then you add a member job so it has one item. You then assign this 1 item array to globalJobs = memberJobs so they are exactly the same. This assignment occurs over and over again for the loop for case let child as DataSnapshot in snapshot.children that is why you are only left with 1 item at then end.
You should declare var memberJobs: [String] = [] before the snapshot loop and assign globalJobs = memberJobs after the snapshot loop is done.

Firebase query for specific user information is not working. How can I resolve this issue?

I have written a firebase query that searches for the level of a user and then changes the level-label on the view controller. However, this query is giving me level for the first user in my Firebase db instead of the level for the current user. How can I resolve this issue?
#IBOutlet weak var levelLabel: UILabel!
var refUser:DatabaseReference?
override func viewDidLoad() {
super.viewDidLoad()
refUser = Database.database().reference().child("userInfo");
let userID = Auth.auth().currentUser!.uid
let query = refUser?.queryOrdered(byChild: "userId").queryEqual(toValue: "\(userID)")
query?.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let childSnap = child as! DataSnapshot
var dict = childSnap.value as! [String: Any]
let level=dict["level"] as! String
self.levelLabel.text=level
}
})
}
Photo of Firebase DB
try in such way:
query?.observeSingleEvent(of: .value, with: { snapshot in
guard let values = snapshot.value as? [String: Any],
let level = values["level"] as? String else {
return
}
DispatchQueue.main.async {
self.levelLabel.text = level
}
})

iOS Firebase asynchronous data retrieval into Array

I try to retrieve data from Firebase into Array. Because it runs asynchronously, the results that I want to show in my CollectionView is a delay until I switch back and forth. I am very new to asynchronous functions in iOS. Please help me to complete my code.
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
}
})
You need to reload the collection after you retrieve the data so after the for loop call reloadData()
for child in result {
}
self.collectionView.reloadData()
//
func getValueFromDatabase(completion: #escaping (_ status: Bool) -> Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion(true)
}
else {
completion(false)
}
})
}
//
self.getValueFromDatabase { (status) in
if status {
// success
}
}
I'm working with Firebase in my project right now. I would suggest the following solution: wrap the database observer in a distinct function which gets completion block as a parameter.
func getValueFromDatabase(completion: ()->Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion()
}
})
}
This way you can call the function from anywhere providing the desired action after fetching data from db is finished:
getValueFromDatabase(completion:{
self.collectionView.reloadData() //or any other action you want to fulfil
})

How to Append Filter Data on Array from Firebase in Swift

Please find my code below. How can we append filter data on array from Firebase?
var childrenList = [DatabaseList]()
let ref = Database.database().reference(withPath: "Messages")
let query = ref.queryOrdered(byChild: "VideoID").queryEqual(toValue: "12345").observe(.value, with: { (snapshot) in
for childSnapshot in snapshot.children{
print(childSnapshot)
self.childrenList.append(snapshot)
}
})
DispatchQueue.main.async {
self.tableView.reloadData()
}
let ref = Database.database().reference(withPath: "Messages")
let query = ref.queryOrdered(byChild: "VideoID").queryEqual(toValue: "12345").observe(.value, with: { (snapshot) in
print(snapshot)
for (childSnapshotId, childSnapshotValue) in snapshot {
if let dataListDict = childSnapshotValue as? [String: AnyObject] {
//Init you newModel with the dataListDict here
let newModel = DatabaseList(dict: dataListDict)
print(childSnapshot)
self.childrenList.append(newModel)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
class DatabaseList : NSObject {
var messageBody : String?
var name : String?
var videoID : String?
init(dict: [String: AnyObject]) {
messageBody = dict["MessageBody"]
name = dict["Name"]
videoID = dict["videoID"]
}
}
Your query is correct but there are few mistakes in finishing block.
self.childrenList.append(snapshot) snapshot is an instance of DataSnapshot not a DatabaseList so you can not append it like this.
for childSnapshot in snapshot.children {
/// childSnapshot is an instance of DataSnapshot not a dictionary but its value will be
guard let data = (childSnapshot as! DataSnapshot).value else {continue}
let dataDict = data as! Dictionary<String, Any>
/// Initializing the new object of DatabaseList and passing the values from data
let list: DatabaseList = DatabaseList()
list.messageBody = dataDict["MessageBody"] as? String
list.name = dataDict["Name"] as? String
list.videoID = dataDict["VideoID"] as? String
/// This is correct, and now you can append it to your array.
childrenList.append(list)
}
Apart from this you will have to reload the tableView inside the finishing block not below the block because this is an asynchronous request and data will come later.
Also its always better to check the data existence. snapshot.exists().
One more suggestion if you want to fetch the data just once then do not use .observe use .observeSingleEvent instead. .observe will fire the block every time there is any change at this node.
Here is the full code snippet.
let query = ref.queryOrdered(byChild: "VideoID").queryEqual(toValue: "12345").observe(.value, with: { (snapshot) in
if !snapshot.exists() {
// Data doesn't exist
return
}
for childSnapshot in snapshot.children {
guard let data = (childSnapshot as! DataSnapshot).value else {continue}
let dataDict = data as! Dictionary<String, Any>
let list: DatabaseList = DatabaseList()
list.messageBody = dataDict["MessageBody"] as? String
list.name = dataDict["Name"] as? String
list.videoID = dataDict["VideoID"] as? String
childrenList.append(list)
}
/// Reload your tableView here
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
And expecting the class model like below:
class DatabaseList: NSObject {
var messageBody: String?
var name: String?
var videoID: String?
}

Firebase Query Running At Wrong Time

For some reason when I run the following method, the first query runs after the method completes. I tried using a dispatch block in order to force the query to run first, but the query never runs at all then and the app simply freezes. Let me know if you know what is wrong.
Method without dispatch group:
func loadConversations() {
let ref = FIRDatabase.database().reference()
let convoRef = ref.child("users").child(FIRAuth.auth()!.currentUser!.uid).child("conversations")
var conversationID = [String]()
print(1)
convoRef.queryOrderedByKey().observeSingleEvent(of: .value, with: { (snapshot) in
let enumerator = snapshot.children
print(2)
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(3)
if let id = rest.value as? String{
conversationID.append(id)
print(id)
}
}
})
print(4)
print("size: \(conversationID.count)")
for id in conversationID {
print(5)
ref.child("conversations").queryEqual(toValue: id).observeSingleEvent(of: .value, with: { (snapshot) in
print(6)
if let convo = snapshot.value as? [String : AnyObject] {
print(7)
let conversation = Conversation()
conversation.conversationID = id
conversation.name = "Temporary test name"
self.Conversations.append(conversation)
}
})
ref.removeAllObservers()
}
print(8)
self.conversationTableView.reloadData()
ref.removeAllObservers()
}
This prints:
1
4
size: 0
8
2
3
-KZyMMzXmkQC_OF0T08_
With the dispatch group:
func loadConversations() {
let dispatchGroup = DispatchGroup()
let ref = FIRDatabase.database().reference()
let convoRef = ref.child("users").child(FIRAuth.auth()!.currentUser!.uid).child("conversations")
var conversationID = [String]()
print(1)
dispatchGroup.enter()
convoRef.queryOrderedByKey().observeSingleEvent(of: .value, with: { (snapshot) in
let enumerator = snapshot.children
print(2)
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(3)
if let id = rest.value as? String{
conversationID.append(id)
print(id)
dispatchGroup.leave()
}
}
})
print(4)
dispatchGroup.wait()
print("size: \(conversationID.count)")
for id in conversationID {
print(5)
ref.child("conversations").queryEqual(toValue: id).observeSingleEvent(of: .value, with: { (snapshot) in
print(6)
if let convo = snapshot.value as? [String : AnyObject] {
print(7)
let conversation = Conversation()
conversation.conversationID = id
conversation.name = "Temporary test name"
self.Conversations.append(conversation)
}
})
}
print(8)
self.conversationTableView.reloadData()
ref.removeAllObservers()
}
This prints
1
4
but then it just freezes and waits. The query never runs.
I am not sure why the query just does not appear to be entered. When the query is entered, it works perfectly fine, but it is entered too late. Any help is greatly appreciated. Thanks!
This is simply because Firebase queries are executed on a background thread as it is essentially a network call. Hence the response comes after your method completes, otherwise the UI will be blocked until a response comes from Firebase
You need to write a closure inside your query response to execute a block of code as soon as you get the response.
func loadConversations(completion:#escaping (Array<String>) -> Void) -> Void {
let ref = FIRDatabase.database().reference()
let convoRef = ref.child("users").child(FIRAuth.auth()!.currentUser!.uid).child("conversations")
var conversationID = [String]()
print(1)
convoRef.queryOrderedByKey().observeSingleEvent(of: .value, with: { (snapshot) in
let enumerator = snapshot.children
print(2)
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(3)
if let id = rest.value as? String{
conversationID.append(id)
print(id)
}
}
completion(conversationID)
})
}
This will send your call back to wherever it was called from and inside
loadConversations { (array) in
//do something with this value and execute next query
}

Resources