Merging multiple Firebase Snapshot Data in the same tableView - swift

In my App I want to show a feed which generates a tableView depending on which groups the user subscribed. In the background two Firebase request are done and then converted to a tableView.
My Code shows the content correct. But there are two issues. The order is wrong. It should be 4->3->2->1 (based on timestamp) and right now it is random. Also if I click on a comment the didSeleceRowAt works totally wrong.
How can I manage to put the result in the same tableView correctly?
func datenBankAbfrage() {
ref = Database.database().reference().child("placeID/h77e24d95a5479ed7588")
ref.queryOrdered(byChild: "userTime").queryLimited(toLast: 10).observe(
DataEventType.value,
with: { (snapshot) in
self.ref = Database.database().reference().child(
"placeID/vh-b83b6e4475e04e3fbaa647d23b")
self.ref.queryOrdered(byChild: "userTime").queryLimited(toLast: 10).observe(
DataEventType.value,
with: { (snapshot2) in
for video in snapshot.children.allObjects as! [DataSnapshot] {
let Object1 = video.value as? [String: AnyObject]
let userName = Object1?["userName"]
let userGroup = Object1?["userGroup"]
let userComment = Object1?["userComment"]
let userTime = Object1?["userTime"]
let userLikes = Object1?["userLikes"]
let commentId = Object1?["commentId"]
ViewComments.commentIDNew = commentId as! String
let video = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! Int,
userLikes: userLikes as! Int, commentId: commentId as! String)
self.table.insert(video, at: 0)
// self.table.append(video)
self.tableView.reloadData()
}
for video in snapshot2.children.allObjects as! [DataSnapshot] {
let Object2 = video.value as? [String: AnyObject]
let userName = Object2?["userName"]
let userGroup = Object2?["userGroup"]
let userComment = Object2?["userComment"]
let userTime = Object2?["userTime"]
let userLikes = Object2?["userLikes"]
let commentId = Object2?["commentId"]
ViewComments.commentIDNew = commentId as! String
let video2 = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! Int,
userLikes: userLikes as! Int, commentId: commentId as! String)
self.table.insert(video2, at: 0)
// self.table.append(video)
self.tableView.reloadData()
}
})
})
}
class importComment {
var userName: String?
var userID: String?
var userGroup: String?
var userComment: String?
var userTime: Int?
var userLikes: Int?
var commentId: String?
init(
userName: String?, userGroup: String?, userComment: String?, userTime: Int?,
userLikes: Int?, commentId: String?
) {
self.userName = userName
self.userGroup = userGroup
self.userComment = userComment
self.userTime = userTime
self.userLikes = userLikes
self.commentId = commentId
}
}

No need to fetch data in Loops. As you're calling asynchronous functions in a loop, there is no guarantee of order.
You should use DispatchGroup. Here is how execution will go:
Create a DispatchGroup instance.
Add SnapShot-1 Fetch request to the Dispatch group.
Now add SnapShot-2 Fetch request to the Dispatch group.
Notify() will only be called after both the tasks has been completed. So you should prepare your data there.
Reload Table after data preparation.
Here is an example implementation:
func datenBankAbfrage() {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
ref = Database.database().reference().child("placeID/h77e24d95a5479ed7588")
ref.queryOrdered(byChild: "userTime").queryLimited(toLast: 10).observe(
DataEventType.value,
with: { (snapshot) in
for video in snapshot.children.allObjects as! [DataSnapshot] {
let Object1 = video.value as? [String: AnyObject]
let userName = Object1?["userName"]
let userGroup = Object1?["userGroup"]
let userComment = Object1?["userComment"]
let userTime = Object1?["userTime"]
let userLikes = Object1?["userLikes"]
let commentId = Object1?["commentId"]
ViewComments.commentIDNew = commentId as! String
let video = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! Int,
userLikes: userLikes as! Int, commentId: commentId as! String)
self.table.append(video)
}
dispatchGroup.leave()
})
dispatchGroup.enter()
self.ref = Database.database().reference().child(
"placeID/vh-b83b6e4475e04e3fbaa647d23b")
self.ref.queryOrdered(byChild: "userTime").queryLimited(toLast: 10).observe(
DataEventType.value,
with: { (snapshot2) in
for video in snapshot2.children.allObjects as! [DataSnapshot] {
let Object2 = video.value as? [String: AnyObject]
let userName = Object2?["userName"]
let userGroup = Object2?["userGroup"]
let userComment = Object2?["userComment"]
let userTime = Object2?["userTime"]
let userLikes = Object2?["userLikes"]
let commentId = Object2?["commentId"]
ViewComments.commentIDNew = commentId as! String
let video2 = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! Int,
userLikes: userLikes as! Int, commentId: commentId as! String)
self.table.append(video)
}
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .main) {
// This is invoked when all the tasks in the group is completed.
self.prepareDatasource()
}
}
func prepareDatasource() {
self.table.sort(by: { $0.userTime ?? 0 > $1.userTime ?? 1}) // Sorting data
DispatchQueue.main.async {
self.tableView.reloadData()
}
}

Related

Reloading tableView after liking a button in cell

My tableView shows some user comments loaded via Firebase Database. The tableView contains also a prototype cell. First of all loadPosts() gets executed. It loads every post and also goes to the user and takes the profile picture which is up to date and at last it sorts them after time.
Now the user has the option to like a post. In the extension UITableViewDelegate I created a cell.buttonAction. Here it opens into the users account and looks if he already liked the post with a specific id. If not it adds +1. If it did already it takes one -1.
The code itself works. But I have huge performance issues. Sometime the tableView gets updated immidiately. Sometimes I have to manually refresh it. As you can see right now I use observeSingleEvent. Should I better use observe instead? But then it crashes sometimes if someone posts and I have the same tableView opened. I also don't want it to reload every time the whole tableView if someone liked a post.
I also created a Video. Maybe it helps. I really do not know how I can solve that problem.
func loadPosts() {
let placeIdFromSearch = ViewController.placeidUebertragen
ref = Database.database().reference().child("placeID/\(placeIdFromSearch)")
ref.queryOrdered(byChild: "userTime").queryLimited(toLast: 10).observeSingleEvent(of: DataEventType.value, with: {(snapshot) in
self.table.removeAll()
for video in snapshot.children.allObjects as! [DataSnapshot] {
let Object = video.value as? [String: AnyObject]
let timestampDate = NSDate(timeIntervalSince1970: Double(Object?["userTime"] as! NSNumber)/1000)
...
let time = dateFormatter.string(from: timestampDate as Date)
...
let userTimeForArranging = Object?["userTime"]
ViewComments.commentIDNew = commentId as! String
getProfilePicture()
func getProfilePicture(){
self.ref = Database.database().reference()
self.ref.child("user/\(userID!)").observe (.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userImage = value?["picture"] as? String ?? ""
let userName = value?["firstname"] as? String ?? ""
let video = importComment(userName: userName as! String, userGroup: userGroup as! String, userComment: userComment as! String, userTime: userTime as! String, userLikes: userLikes as! Int, commentId: commentId as! String, placeID: placeID as! String, kommentarCount: kommentarCount as! Int, userImage: userImage as! String, userTimeForArranging: userTimeForArranging as! Int)
self.table.insert(video, at: 0)
self.table = self.table.sorted(by: { $0.userTimeForArranging! > $1.userTimeForArranging! })
self.tableView.reloadData()
})
}
}
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
let video: importComment
video = table[indexPath.row]
cell.layoutMargins = UIEdgeInsets.zero
...
cell.userImage.image = UIImage(url: URL(string: video.userImage!))
cell.buttonAction = {
getClickedRow(arg: true, completion: { (success) -> Void in
if success {
self.loadPosts()
} else {
}
})
func getClickedRow(arg: Bool, completion: #escaping (Bool) -> ()) {
let selectedIndexPath = self.table[indexPath.row].commentId!
ViewSubComments.subCommentIdNew = selectedIndexPath
completion(arg)
}
}
return cell
}
From Prototype cell:
#IBAction func buttonTapped(_ sender: UIButton) {
buttonAction?()
let ref = Database.database().reference()
let userID = Auth.auth().currentUser!.uid
ref.child("user/\(userID)/places/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
getLikeAndMinusOne ()
let database = Database.database().reference()
database.child("user/\(userID)/places/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").removeValue()
}else{
getLikeAndAddOne ()
let database = Database.database().reference()
let object: [String: Any] = [
"like": "true",]
database.child("user/\(userID)/places/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").setValue(object)
}
})
func getLikeAndAddOne () {
let database = Database.database().reference()
database.child("placeID/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").observeSingleEvent(of: .value, with: { (snapshot) in
let userDict = snapshot.value as! [String: Any]
let userLikes = userDict["userLikes"] as! Int
var userLikesNeu = Int(userLikes)
userLikesNeu = Int(userLikes) + 1
database.child("placeID/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").updateChildValues(["userLikes": userLikesNeu])
})}
func getLikeAndMinusOne () {
let database = Database.database().reference()
database.child("placeID/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").observeSingleEvent(of: .value, with: { (snapshot) in
let userDict = snapshot.value as! [String: Any]
let userLikes = userDict["userLikes"] as! Int
var userLikesNeu = Int(userLikes)
userLikesNeu = Int(userLikes) - 1
database.child("placeID/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").updateChildValues(["userLikes": userLikesNeu])
})}
}

passing string from firebase realtime database request

How can I pass the string userImage which I get from a firebase realtime database request? I can not just use return userImage. It says Unexpected non-void return value in void function. I want the userImage as result and paste it to XXXXXX.
...
func datenBankAbfrage() {
print("datenbankabfrage getriggert")
dic = [:]
for name in myFeed.myArray1[0..<myFeed.myArray1.count] {
ref = Database.database().reference().child("placeID/\(name)")
ref.observe(.value) { (snapshot) in
self.dic[name] = []
var specificNameVideos = [importComment]()
for video in snapshot.children.allObjects as! [DataSnapshot] {
let Object1 = video.value as? [String: AnyObject]
let timestampDate = NSDate(timeIntervalSince1970: Double(Object1?["userTime"] as! NSNumber)/1000)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = NSTimeZone.local
dateFormatter.dateFormat = "dd.MM.YY hh:mm"
let time = dateFormatter.string(from: timestampDate as Date)
print(time + "Uhr")
let userName = Object1?["userName"]
...
let kommentarCount = Object1?["kommentarCount"]
let userID = Object1?["userID"]
//print(userID!)
ViewComments.commentIDNew = commentId as! String
func wertBerechnen(completion: #escaping (String) -> Void) {
self.ref = Database.database().reference()
self.ref.child("user/\(userID!)").observeSingleEvent (of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userImage = value?["picture"] as? String ?? ""
completion(userImage)
})
}
wertBerechnen { userImage in
let video = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! String,
userLikes: userLikes as! Int, commentId: commentId as! String, placeID: placeID as! String, kommentarCount: kommentarCount as? Int, userImage: userImage)
specificNameVideos.append(video)
}
}
self.dic[name] = specificNameVideos
let tem = Array(self.dic.values).flatMap { $0 } // You can also sort allComments with userTime property
self.table = tem.sorted { $0.userTime! > $1.userTime! }
self.tableView.reloadData()
}
myFeed.myArray1 = []
}
...
You can use a completion handler to return the userImage string:
func wertBerechnen(completion: #escaping (String) -> Void) {
self.ref = Database.database().reference()
self.ref.child("user/\(userID!)").observeSingleEvent (of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userImage = value?["picture"] as? String ?? ""
completion(userImage)
})
}
Then you can call it like this:
wertBerechnen { userImage in
let video = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! String,
userLikes: userLikes as! Int, commentId: commentId as! String, placeID: placeID as! String, kommentarCount: kommentarCount as? Int, userImage: userImage)
specificNameVideos.append(video)
}
self.dic[name] = specificNameVideos
let tem = Array(self.dic.values).flatMap { $0 }
self.table = tem.sorted { $0.userTime! > $1.userTime! }
self.tableView.reloadData()
}
}
You need to reload the tableView data inside the wertBerechnen completion handler:
func wertBerechnen(completion: #escaping (String) -> Void) {
self.ref = Database.database().reference()
self.ref.child("user/\(userID!)").observeSingleEvent (of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userImage = value?["picture"] as? String ?? ""
completion(userImage)
})
}
Then you can call it like this:
wertBerechnen { userImage in
let video = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! String,
userLikes: userLikes as! Int, commentId: commentId as! String, placeID: placeID as! String, kommentarCount: kommentarCount as? Int, userImage: userImage)
specificNameVideos.append(video)
self.tableView.reloadData()
}
self.dic[name] = specificNameVideos
let tem = Array(self.dic.values).flatMap { $0 }
self.table = tem.sorted { $0.userTime! > $1.userTime! }
}
}

Is it possible to read from multiple child nodes?

I want to read all three data sourcing from "Arts & Humanities" and "Beauty & Style". Is this possible?
Let ref = Database.database().reference().child("posts")
//CODE A: Pulls 2 snapshot, but doesn't display anything
let ref = Database.database().reference().child("posts").child("Arts & Humanities")
//CODE B: only pulls up the two feeds but excludes beauty and style. Vice versa
//Below is the listener code I have. This works only works with CODE B above; but ideally id like to read the post under "Beauty & Style" as well.
postsRef.observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [PostModel]()
for child in snapshot.children {
print(snapshot.childrenCount)
if let childSnapshot = child as? DataSnapshot,
let dict = childSnapshot.value as? [String:Any],
let author = dict["author"] as? [String:Any],
let uid = author["uid"] as? String,
let username = author["username"] as? String,
let fullname = author["fullname"] as? String,
let patthToImage = author["patthToImage"] as? String,
let url = URL(string:patthToImage),
let pathToImage = dict["pathToImage"] as? String,
let likes = dict["likes"] as? Int,
let postID = dict["postID"] as? String,
let message = dict["message"] as? String,
let genre = dict["genre"] as? String,
let timestamp = dict["timestamp"] as? Double {
if childSnapshot.key != lastPost?.id {
let userProfile = UserProfile(uid: uid, fullname: fullname, username: username, patthToImage: url)
let post = PostModel(genre: genre, likes: likes, message: message, pathToImage: pathToImage, postID: postID, userID: pathToImage, timestamp: timestamp, id: childSnapshot.key, author: userProfile)
tempPosts.insert(post, at: 0)
if lastPost?.id != nil {
lastPostIdChecker = lastPost!.id
}
}
}
}
return completion(tempPosts)
})

Swift, How to Get Completion Handler working as I Wish?

I am fetching user information from Firebase and my goal is to fetch a batch of users and then display them on the screen one at a time in a card stack. To do this i use a completion handler. However my code inside the completion handler runs before the fetch of all users is done.
Thank you for any help.
Here is my code. I want "fetchOneUser()" to run when "fetchAllUsers" is done:
fetchAllUsers(completion: { message in
print(message)
print("FetchOneUser")
self.fetchOneUser()
})
Here is fetchAllUser function:
func fetchAllUsers(completion: #escaping (_ message: String) -> Void){
//User or advertiser?
Database.database().reference(withPath: "Advertiser").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
myAdvertiserVar.advertiser = true
self.currentUserKind = "Advertiser"
self.otherUserKind = "Users"
}
else{
self.currentUserKind = "Users"
self.otherUserKind = "Advertiser"
}
// Fetch
let query = self.ref?.child(self.otherUserKind).queryOrdered(byChild: "email")
query?.observeSingleEvent(of: .value) {
(snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
let id = child.key
//If Already Accepted, don't fetch
Database.database().reference(withPath: self.currentUserKind).child(self.uid).child("Accepted").child(id).observeSingleEvent(of: .value, with: {(accepted) in
if accepted.exists(){
print("\(id) är redan Accepted")
}
else{
if myAdvertiserVar.advertiser == true{
let value = child.value as? NSDictionary
let username = value?["Username"] as? String
let occupation = value?["Occupation"] as? String
let age = value?["Age"] as? String
let bio = value?["Bio"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
else{
let value = child.value as? NSDictionary
let username = value?["Owner"] as? String
let occupation = value?["Location"] as? String
let age = value?["Rent"] as? String
let bio = value?["About"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
}
})
}
print(self.usersArray.count)
completion("Users list fetched")
}
})
}
You need to use DispatchGroup as the inner calls are asynchronous
func fetchAllUsers(completion: #escaping (_ message: String) -> Void){
//User or advertiser?
Database.database().reference(withPath: "Advertiser").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
myAdvertiserVar.advertiser = true
self.currentUserKind = "Advertiser"
self.otherUserKind = "Users"
}
else{
self.currentUserKind = "Users"
self.otherUserKind = "Advertiser"
}
// Fetch
let query = self.ref?.child(self.otherUserKind).queryOrdered(byChild: "email")
query?.observeSingleEvent(of: .value) {
(snapshot) in
let g = DispatchGroup()
for child in snapshot.children.allObjects as! [DataSnapshot] {
let id = child.key
//If Already Accepted, don't fetch
g.enter()
Database.database().reference(withPath: self.currentUserKind).child(self.uid).child("Accepted").child(id).observeSingleEvent(of: .value, with: {(accepted) in
if accepted.exists(){
print("\(id) är redan Accepted")
}
else{
if myAdvertiserVar.advertiser == true{
let value = child.value as? NSDictionary
let username = value?["Username"] as? String
let occupation = value?["Occupation"] as? String
let age = value?["Age"] as? String
let bio = value?["Bio"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
else{
let value = child.value as? NSDictionary
let username = value?["Owner"] as? String
let occupation = value?["Location"] as? String
let age = value?["Rent"] as? String
let bio = value?["About"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
}
g.leave()
})
}
g.notify(queue: .main, execute: {
print(self.usersArray.count)
completion("Users list fetched")
})
}
})
}
Based on Firebase documentation:
Firebase use refrence() method to get a database refrence for the root of your real time database asynchronous.
this means that result takes more time to fetch than for loop, in this situation your for loop finishes and completion block calls and takes you out of method, then result of your request will return.
your code should look like
var firebaseDatabaseRefrence: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
Database.database().reference(withPath: self.currentUserKind)
}
func someMethod() {
self.firebaseDatabaseRefrence
.child(self.uid)
.child("Accepted")
.child(id).observeSingleEvent(of: .value, with: {(accepted) in
}

How do I filter data from my Firebase Database?

How do I change the fetchAllPosts function in my networking file and in the homeviewController so that I only get posts from the database that match the UID of the user I’m following with the post.UID, so my feed is filled with posts of users I follow?
Here is the reference showing how I make a new follower in the database:
let followingRef = "following/" + (self.loggedInUserData?["uid"] as! String) + "/" + (self.otherUser?["uid"] as! String)
Here is the post structure in the Firebase database
posts
-Ke4gQKIbow10WdLYMTL (generated key)
postDate:
postId:
postPicUrl:
postText:
postTit:
type:
uid: looking to match this
Here is the current fetchAllPosts function in the networking file
func fetchAllPosts(completion: #escaping ([Post])->()) {
let postRef = self.dataBaseRef.child("posts")
postRef.observe(.value, with: { (posts) in
var resultsArray = [Post]()
for post in posts.children {
let post = Post(snapshot: post as! FIRDataSnapshot)
resultsArray.append(post)
}
completion(resultsArray)
}) { (error) in
print(error.localizedDescription)
}
}
here is the fetchAllPosts function in the homeViewController
private func fetchAllPosts(){
authService.fetchAllPosts {(posts) in
self.postsArray = posts
self.postsArray.sort(by: { (post1, post2) -> Bool in
Int(post1.postDate) > Int(post2.postDate)
})
self.tableView.reloadData()
}
}
Here is my post structure in my swift file:
struct Post {
var username: String!
var uid: String!
var postId: String!
var type: String
var postText: String
var postTit: String
var postPicUrl: String!
var postDate: NSNumber!
var ref: FIRDatabaseReference!
var key: String = ""
init(postId: String,postText: String, postTit:String, postDate:NSNumber, postPicUrl: String?, type:String, uid: String, key: String = ""){
self.postText = postText
self.postTit = postTit
self.postPicUrl = postPicUrl
self.type = type
self.uid = uid
self.postDate = postDate
self.postId = postId
}
init(snapshot: FIRDataSnapshot){
self.ref = snapshot.ref
self.key = snapshot.key
self.postId = (snapshot.value! as! NSDictionary)["postId"] as! String
self.type = (snapshot.value! as! NSDictionary)["type"] as! String
self.postPicUrl = (snapshot.value! as! NSDictionary)["postPicUrl"] as! String
self.postDate = (snapshot.value! as! NSDictionary)["postDate"] as! NSNumber
self.postTit = (snapshot.value! as! NSDictionary)["postTit"] as! String
self.uid = (snapshot.value! as! NSDictionary)["uid"] as! String
self.postText = (snapshot.value! as! NSDictionary)["postText"] as! String
}
func toAnyObject() -> [String: Any] {
return ["postId":self.postId,"type":self.type, "postPicUrl":self.postPicUrl,"postDate":self.postDate, "postTit":self.postTit,"uid": self.uid, "postText":self.postText,]
}
}
Did you use this;
postRef.queryOrdered(byChild: "uid").queryEqual(toValue: yourUid).observe(.value, with: {
snapshot in
if let shot = snapshot {
let post = Post(snapshot: post as! FIRDataSnapshot)
}
}
This returns data you are looking for.